summaryrefslogtreecommitdiff
path: root/src/v4l2
diff options
context:
space:
mode:
Diffstat (limited to 'src/v4l2')
-rwxr-xr-xsrc/v4l2/libcamerify.in47
-rw-r--r--src/v4l2/meson.build27
-rw-r--r--src/v4l2/v4l2_camera.cpp157
-rw-r--r--src/v4l2/v4l2_camera.h87
-rw-r--r--src/v4l2/v4l2_camera_file.cpp52
-rw-r--r--src/v4l2/v4l2_camera_file.h40
-rw-r--r--src/v4l2/v4l2_camera_proxy.cpp869
-rw-r--r--src/v4l2/v4l2_camera_proxy.h113
-rw-r--r--src/v4l2/v4l2_compat.cpp93
-rw-r--r--src/v4l2/v4l2_compat_manager.cpp129
-rw-r--r--src/v4l2/v4l2_compat_manager.h23
11 files changed, 1209 insertions, 428 deletions
diff --git a/src/v4l2/libcamerify.in b/src/v4l2/libcamerify.in
new file mode 100755
index 00000000..c4ea273f
--- /dev/null
+++ b/src/v4l2/libcamerify.in
@@ -0,0 +1,47 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+help() {
+ echo "$0: Load an application with libcamera V4L2 compatibility layer preload"
+ echo " $0 [OPTIONS...] executable [args]"
+ echo " -d, --debug Increase log level"
+}
+
+debug=0
+while [ $# -gt 0 ]; do
+ case $1 in
+ -d|--debug)
+ debug=$((debug+1))
+ ;;
+ -h)
+ help;
+ exit 0
+ ;;
+ --)
+ shift;
+ break;;
+ -*)
+ echo "Unrecognised option: $1";
+ help;
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+[ $debug -gt 0 ] && loglevel=V4L2Compat:0
+[ $debug -gt 1 ] && loglevel=0
+[ "$loglevel" != "" ] && export LIBCAMERA_LOG_LEVELS=$loglevel
+
+if [ "$LD_PRELOAD" = "" ] ; then
+ LD_PRELOAD='@LIBCAMERA_V4L2_SO@'
+else
+ LD_PRELOAD="$LD_PRELOAD "'@LIBCAMERA_V4L2_SO@'
+fi
+
+export LD_PRELOAD
+
+exec "$@"
diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build
index efab968f..2c040414 100644
--- a/src/v4l2/meson.build
+++ b/src/v4l2/meson.build
@@ -1,5 +1,14 @@
+# SPDX-License-Identifier: CC0-1.0
+
+v4l2_enabled = get_option('v4l2').allowed()
+
+if not v4l2_enabled
+ subdir_done()
+endif
+
v4l2_compat_sources = files([
'v4l2_camera.cpp',
+ 'v4l2_camera_file.cpp',
'v4l2_camera_proxy.cpp',
'v4l2_compat.cpp',
'v4l2_compat_manager.cpp',
@@ -13,6 +22,8 @@ v4l2_compat_cpp_args = [
# file operations, disable transparent large file support.
'-U_FILE_OFFSET_BITS',
'-D_FILE_OFFSET_BITS=32',
+ '-D_LARGEFILE64_SOURCE',
+ '-U_TIME_BITS',
'-fvisibility=hidden',
]
@@ -20,6 +31,18 @@ v4l2_compat = shared_library('v4l2-compat',
v4l2_compat_sources,
name_prefix : '',
install : true,
- include_directories : libcamera_internal_includes,
- dependencies : [ libcamera_dep, libdl ],
+ install_dir : libcamera_libexecdir,
+ dependencies : [libcamera_private, libdl],
cpp_args : v4l2_compat_cpp_args)
+
+# Provide a wrapper script to support easily loading applications with the V4L2
+# adaptation layer.
+
+cdata = configuration_data()
+cdata.set('LIBCAMERA_V4L2_SO', get_option('prefix') / libcamera_libexecdir / 'v4l2-compat.so')
+
+configure_file(input : 'libcamerify.in',
+ output : 'libcamerify',
+ configuration : cdata,
+ install_dir : get_option('bindir'),
+ install_tag : 'bin')
diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp
index ecbb70ac..94d138cd 100644
--- a/src/v4l2/v4l2_camera.cpp
+++ b/src/v4l2/v4l2_camera.cpp
@@ -1,22 +1,26 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_camera.cpp - V4L2 compatibility camera
+ * V4L2 compatibility camera
*/
#include "v4l2_camera.h"
#include <errno.h>
+#include <unistd.h>
-#include "log.h"
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
using namespace libcamera;
-LOG_DECLARE_CATEGORY(V4L2Compat);
+LOG_DECLARE_CATEGORY(V4L2Compat)
V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)
- : camera_(camera), isRunning_(false), bufferAllocator_(nullptr)
+ : camera_(camera), controls_(controls::controls), isRunning_(false),
+ bufferAllocator_(nullptr), efd_(-1), bufferAvailableCount_(0)
{
camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);
}
@@ -26,9 +30,8 @@ V4L2Camera::~V4L2Camera()
close();
}
-int V4L2Camera::open()
+int V4L2Camera::open(StreamConfiguration *streamConfig)
{
- /* \todo Support multiple open. */
if (camera_->acquire() < 0) {
LOG(V4L2Compat, Error) << "Failed to acquire camera";
return -EINVAL;
@@ -42,31 +45,38 @@ int V4L2Camera::open()
bufferAllocator_ = new FrameBufferAllocator(camera_);
+ *streamConfig = config_->at(0);
return 0;
}
void V4L2Camera::close()
{
+ requestPool_.clear();
+
delete bufferAllocator_;
bufferAllocator_ = nullptr;
camera_->release();
}
-void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)
+void V4L2Camera::bind(int efd)
{
- *streamConfig = config_->at(0);
+ efd_ = efd;
+}
+
+void V4L2Camera::unbind()
+{
+ efd_ = -1;
}
std::vector<V4L2Camera::Buffer> V4L2Camera::completedBuffers()
{
std::vector<Buffer> v;
- bufferLock_.lock();
+ MutexLocker lock(bufferLock_);
for (std::unique_ptr<Buffer> &metadata : completedBuffers_)
v.push_back(*metadata.get());
completedBuffers_.clear();
- bufferLock_.unlock();
return v;
}
@@ -84,7 +94,17 @@ void V4L2Camera::requestComplete(Request *request)
completedBuffers_.push_back(std::move(metadata));
bufferLock_.unlock();
- bufferSema_.release();
+ uint64_t data = 1;
+ int ret = ::write(efd_, &data, sizeof(data));
+ if (ret != sizeof(data))
+ LOG(V4L2Compat, Error) << "Failed to signal eventfd POLLIN";
+
+ request->reuse();
+ {
+ MutexLocker locker(bufferMutex_);
+ bufferAvailableCount_++;
+ }
+ bufferCV_.notify_all();
}
int V4L2Camera::configure(StreamConfiguration *streamConfigOut,
@@ -118,29 +138,65 @@ int V4L2Camera::configure(StreamConfiguration *streamConfigOut,
return 0;
}
+int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,
+ const Size &size,
+ StreamConfiguration *streamConfigOut)
+{
+ std::unique_ptr<CameraConfiguration> config =
+ camera_->generateConfiguration({ StreamRole::Viewfinder });
+ StreamConfiguration &cfg = config->at(0);
+ cfg.size = size;
+ cfg.pixelFormat = pixelFormat;
+ cfg.bufferCount = 1;
+
+ CameraConfiguration::Status validation = config->validate();
+ if (validation == CameraConfiguration::Invalid)
+ return -EINVAL;
+
+ *streamConfigOut = cfg;
+
+ return 0;
+}
+
int V4L2Camera::allocBuffers(unsigned int count)
{
- Stream *stream = *camera_->streams().begin();
+ Stream *stream = config_->at(0).stream();
- return bufferAllocator_->allocate(stream);
+ int ret = bufferAllocator_->allocate(stream);
+ if (ret < 0)
+ return ret;
+
+ for (unsigned int i = 0; i < count; i++) {
+ std::unique_ptr<Request> request = camera_->createRequest(i);
+ if (!request) {
+ requestPool_.clear();
+ return -ENOMEM;
+ }
+ requestPool_.push_back(std::move(request));
+ }
+
+ return ret;
}
void V4L2Camera::freeBuffers()
{
- Stream *stream = *camera_->streams().begin();
+ pendingRequests_.clear();
+ requestPool_.clear();
+
+ Stream *stream = config_->at(0).stream();
bufferAllocator_->free(stream);
}
-FileDescriptor V4L2Camera::getBufferFd(unsigned int index)
+int V4L2Camera::getBufferFd(unsigned int index)
{
- Stream *stream = *camera_->streams().begin();
+ Stream *stream = config_->at(0).stream();
const std::vector<std::unique_ptr<FrameBuffer>> &buffers =
bufferAllocator_->buffers(stream);
if (buffers.size() <= index)
- return FileDescriptor();
+ return -1;
- return buffers[index]->planes()[0].fd;
+ return buffers[index]->planes()[0].fd.get();
}
int V4L2Camera::streamOn()
@@ -148,15 +204,17 @@ int V4L2Camera::streamOn()
if (isRunning_)
return 0;
- int ret = camera_->start();
+ int ret = camera_->start(&controls_);
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
+ controls_.clear();
+
isRunning_ = true;
- for (std::unique_ptr<Request> &req : pendingRequests_) {
+ for (Request *req : pendingRequests_) {
/* \todo What should we do if this returns -EINVAL? */
- ret = camera_->queueRequest(req.release());
+ ret = camera_->queueRequest(req);
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
}
@@ -168,27 +226,35 @@ int V4L2Camera::streamOn()
int V4L2Camera::streamOff()
{
- /* \todo Restore buffers to reqbufs state? */
- if (!isRunning_)
+ if (!isRunning_) {
+ for (std::unique_ptr<Request> &req : requestPool_)
+ req->reuse();
+
return 0;
+ }
+
+ pendingRequests_.clear();
int ret = camera_->stop();
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
- isRunning_ = false;
+ {
+ MutexLocker locker(bufferMutex_);
+ isRunning_ = false;
+ }
+ bufferCV_.notify_all();
return 0;
}
int V4L2Camera::qbuf(unsigned int index)
{
- std::unique_ptr<Request> request =
- std::unique_ptr<Request>(camera_->createRequest(index));
- if (!request) {
- LOG(V4L2Compat, Error) << "Can't create request";
- return -ENOMEM;
+ if (index >= requestPool_.size()) {
+ LOG(V4L2Compat, Error) << "Invalid index";
+ return -EINVAL;
}
+ Request *request = requestPool_[index].get();
Stream *stream = config_->at(0).stream();
FrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();
@@ -199,11 +265,13 @@ int V4L2Camera::qbuf(unsigned int index)
}
if (!isRunning_) {
- pendingRequests_.push_back(std::move(request));
+ pendingRequests_.push_back(request);
return 0;
}
- ret = camera_->queueRequest(request.release());
+ request->controls().merge(std::move(controls_));
+
+ ret = camera_->queueRequest(request);
if (ret < 0) {
LOG(V4L2Compat, Error) << "Can't queue request";
return ret == -EACCES ? -EBUSY : ret;
@@ -211,3 +279,28 @@ int V4L2Camera::qbuf(unsigned int index)
return 0;
}
+
+void V4L2Camera::waitForBufferAvailable()
+{
+ MutexLocker locker(bufferMutex_);
+ bufferCV_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(bufferMutex_) {
+ return bufferAvailableCount_ >= 1 || !isRunning_;
+ });
+ if (isRunning_)
+ bufferAvailableCount_--;
+}
+
+bool V4L2Camera::isBufferAvailable()
+{
+ MutexLocker locker(bufferMutex_);
+ if (bufferAvailableCount_ < 1)
+ return false;
+
+ bufferAvailableCount_--;
+ return true;
+}
+
+bool V4L2Camera::isRunning()
+{
+ return isRunning_;
+}
diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h
index 130995d9..9bd161b9 100644
--- a/src/v4l2/v4l2_camera.h
+++ b/src/v4l2/v4l2_camera.h
@@ -1,75 +1,96 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_camera.h - V4L2 compatibility camera
+ * V4L2 compatibility camera
*/
-#ifndef __V4L2_CAMERA_H__
-#define __V4L2_CAMERA_H__
+#pragma once
#include <deque>
-#include <mutex>
-#include <utility>
+#include <memory>
+#include <vector>
+
+#include <libcamera/base/mutex.h>
+#include <libcamera/base/semaphore.h>
+#include <libcamera/base/shared_fd.h>
-#include <libcamera/buffer.h>
#include <libcamera/camera.h>
-#include <libcamera/file_descriptor.h>
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
#include <libcamera/framebuffer_allocator.h>
-#include "semaphore.h"
-
-using namespace libcamera;
-
class V4L2Camera
{
public:
struct Buffer {
- Buffer(unsigned int index, const FrameMetadata &data)
- : index(index), data(data)
+ Buffer(unsigned int index, const libcamera::FrameMetadata &data)
+ : index_(index), data_(data)
{
}
- unsigned int index;
- FrameMetadata data;
+ unsigned int index_;
+ libcamera::FrameMetadata data_;
};
- V4L2Camera(std::shared_ptr<Camera> camera);
+ V4L2Camera(std::shared_ptr<libcamera::Camera> camera);
~V4L2Camera();
- int open();
+ int open(libcamera::StreamConfiguration *streamConfig);
void close();
- void getStreamConfig(StreamConfiguration *streamConfig);
- std::vector<Buffer> completedBuffers();
+ void bind(int efd);
+ void unbind();
- int configure(StreamConfiguration *streamConfigOut,
- const Size &size, const PixelFormat &pixelformat,
+ std::vector<Buffer> completedBuffers() LIBCAMERA_TSA_EXCLUDES(bufferLock_);
+
+ int configure(libcamera::StreamConfiguration *streamConfigOut,
+ const libcamera::Size &size,
+ const libcamera::PixelFormat &pixelformat,
unsigned int bufferCount);
+ int validateConfiguration(const libcamera::PixelFormat &pixelformat,
+ const libcamera::Size &size,
+ libcamera::StreamConfiguration *streamConfigOut);
+
+ libcamera::ControlList &controls() { return controls_; }
+ const libcamera::ControlInfoMap &controlInfo() { return camera_->controls(); }
int allocBuffers(unsigned int count);
void freeBuffers();
- FileDescriptor getBufferFd(unsigned int index);
+ int getBufferFd(unsigned int index);
int streamOn();
int streamOff();
int qbuf(unsigned int index);
- Semaphore bufferSema_;
+ void waitForBufferAvailable() LIBCAMERA_TSA_EXCLUDES(bufferMutex_);
+ bool isBufferAvailable() LIBCAMERA_TSA_EXCLUDES(bufferMutex_);
+
+ bool isRunning();
private:
- void requestComplete(Request *request);
+ void requestComplete(libcamera::Request *request)
+ LIBCAMERA_TSA_EXCLUDES(bufferLock_);
- std::shared_ptr<Camera> camera_;
- std::unique_ptr<CameraConfiguration> config_;
+ std::shared_ptr<libcamera::Camera> camera_;
+ std::unique_ptr<libcamera::CameraConfiguration> config_;
+
+ libcamera::ControlList controls_;
bool isRunning_;
- std::mutex bufferLock_;
- FrameBufferAllocator *bufferAllocator_;
+ libcamera::Mutex bufferLock_;
+ libcamera::FrameBufferAllocator *bufferAllocator_;
- std::deque<std::unique_ptr<Request>> pendingRequests_;
- std::deque<std::unique_ptr<Buffer>> completedBuffers_;
-};
+ std::vector<std::unique_ptr<libcamera::Request>> requestPool_;
+
+ std::deque<libcamera::Request *> pendingRequests_;
+ std::deque<std::unique_ptr<Buffer>> completedBuffers_
+ LIBCAMERA_TSA_GUARDED_BY(bufferLock_);
-#endif /* __V4L2_CAMERA_H__ */
+ int efd_;
+
+ libcamera::Mutex bufferMutex_;
+ libcamera::ConditionVariable bufferCV_;
+ unsigned int bufferAvailableCount_ LIBCAMERA_TSA_GUARDED_BY(bufferMutex_);
+};
diff --git a/src/v4l2/v4l2_camera_file.cpp b/src/v4l2/v4l2_camera_file.cpp
new file mode 100644
index 00000000..d8fe854b
--- /dev/null
+++ b/src/v4l2/v4l2_camera_file.cpp
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * V4L2 compatibility camera file information
+ */
+
+#include "v4l2_camera_file.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/videodev2.h>
+
+#include "v4l2_camera_proxy.h"
+
+using namespace libcamera;
+
+V4L2CameraFile::V4L2CameraFile(int dirfd, const char *path, int efd,
+ bool nonBlocking, V4L2CameraProxy *proxy)
+ : proxy_(proxy), nonBlocking_(nonBlocking), efd_(efd),
+ priority_(V4L2_PRIORITY_DEFAULT)
+{
+ proxy_->open(this);
+
+ if (path[0] != '/') {
+ if (dirfd == AT_FDCWD) {
+ char *cwd = getcwd(nullptr, 0);
+ if (cwd) {
+ description_ = std::string(cwd) + "/";
+ free(cwd);
+ } else {
+ description_ = std::string("(unreachable)/");
+ }
+ } else {
+ description_ = "(dirfd:" + std::to_string(dirfd) + ")/";
+ }
+ }
+
+ description_ += std::string(path) + " (fd:" + std::to_string(efd) + ")";
+}
+
+V4L2CameraFile::~V4L2CameraFile()
+{
+ proxy_->close(this);
+}
+
+const std::string &V4L2CameraFile::description() const
+{
+ return description_;
+}
diff --git a/src/v4l2/v4l2_camera_file.h b/src/v4l2/v4l2_camera_file.h
new file mode 100644
index 00000000..1212989e
--- /dev/null
+++ b/src/v4l2/v4l2_camera_file.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * V4L2 compatibility camera file information
+ */
+
+#pragma once
+
+#include <string>
+
+#include <linux/videodev2.h>
+
+class V4L2CameraProxy;
+
+class V4L2CameraFile
+{
+public:
+ V4L2CameraFile(int dirfd, const char *path, int efd, bool nonBlocking,
+ V4L2CameraProxy *proxy);
+ ~V4L2CameraFile();
+
+ V4L2CameraProxy *proxy() const { return proxy_; }
+
+ bool nonBlocking() const { return nonBlocking_; }
+ int efd() const { return efd_; }
+
+ enum v4l2_priority priority() const { return priority_; }
+ void setPriority(enum v4l2_priority priority) { priority_ = priority; }
+
+ const std::string &description() const;
+
+private:
+ V4L2CameraProxy *proxy_;
+
+ std::string description_;
+ bool nonBlocking_;
+ int efd_;
+ enum v4l2_priority priority_;
+};
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 47d0528b..559ffc61 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -1,70 +1,97 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera
+ * Proxy to V4L2 compatibility camera
*/
#include "v4l2_camera_proxy.h"
#include <algorithm>
-#include <array>
#include <errno.h>
-#include <linux/videodev2.h>
+#include <numeric>
+#include <set>
#include <string.h>
#include <sys/mman.h>
+#include <unistd.h>
+
+#include <linux/videodev2.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/object.h>
+#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
-#include <libcamera/object.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/formats.h>
+
+#include "libcamera/internal/v4l2_pixelformat.h"
-#include "log.h"
-#include "utils.h"
#include "v4l2_camera.h"
+#include "v4l2_camera_file.h"
#include "v4l2_compat_manager.h"
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
using namespace libcamera;
+using namespace std::literals::chrono_literals;
-LOG_DECLARE_CATEGORY(V4L2Compat);
+LOG_DECLARE_CATEGORY(V4L2Compat)
V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
std::shared_ptr<Camera> camera)
: refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
- vcam_(std::make_unique<V4L2Camera>(camera))
+ vcam_(std::make_unique<V4L2Camera>(camera)), owner_(nullptr)
{
querycap(camera);
}
-int V4L2CameraProxy::open(bool nonBlocking)
+int V4L2CameraProxy::open(V4L2CameraFile *file)
{
- LOG(V4L2Compat, Debug) << "Servicing open";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
- int ret = vcam_->open();
- if (ret < 0) {
- errno = -ret;
- return -1;
+ MutexLocker locker(proxyMutex_);
+
+ if (refcount_++) {
+ files_.insert(file);
+ return 0;
}
- nonBlocking_ = nonBlocking;
+ /*
+ * We open the camera here, once, and keep it open until the last
+ * V4L2CameraFile is closed. The proxy is initially not owned by any
+ * file. The first file that calls reqbufs with count > 0 or s_fmt
+ * will become the owner, and no other file will be allowed to call
+ * buffer-related ioctls (except querybuf), set the format, or start or
+ * stop the stream until ownership is released with a call to reqbufs
+ * with count = 0.
+ */
+
+ int ret = vcam_->open(&streamConfig_);
+ if (ret < 0) {
+ refcount_--;
+ return ret;
+ }
- vcam_->getStreamConfig(&streamConfig_);
setFmtFromConfig(streamConfig_);
- sizeimage_ = calculateSizeImage(streamConfig_);
- refcount_++;
+ files_.insert(file);
return 0;
}
-void V4L2CameraProxy::dup()
+void V4L2CameraProxy::close(V4L2CameraFile *file)
{
- refcount_++;
-}
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
-void V4L2CameraProxy::close()
-{
- LOG(V4L2Compat, Debug) << "Servicing close";
+ MutexLocker locker(proxyMutex_);
+
+ files_.erase(file);
+
+ release(file);
if (--refcount_ > 0)
return;
@@ -72,13 +99,24 @@ void V4L2CameraProxy::close()
vcam_->close();
}
-void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
- off_t offset)
+void *V4L2CameraProxy::mmap(V4L2CameraFile *file, void *addr, size_t length,
+ int prot, int flags, off64_t offset)
{
- LOG(V4L2Compat, Debug) << "Servicing mmap";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
- /* \todo Validate prot and flags properly. */
- if (prot != (PROT_READ | PROT_WRITE)) {
+ MutexLocker locker(proxyMutex_);
+
+ /*
+ * Mimic the videobuf2 behaviour, which requires PROT_READ and
+ * MAP_SHARED.
+ */
+ if (!(prot & PROT_READ)) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ if (!(flags & MAP_SHARED)) {
errno = EINVAL;
return MAP_FAILED;
}
@@ -90,14 +128,14 @@ void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
return MAP_FAILED;
}
- FileDescriptor fd = vcam_->getBufferFd(index);
- if (!fd.isValid()) {
+ int fd = vcam_->getBufferFd(index);
+ if (fd < 0) {
errno = EINVAL;
return MAP_FAILED;
}
void *map = V4L2CompatManager::instance()->fops().mmap(addr, length, prot,
- flags, fd.fd(), 0);
+ flags, fd, 0);
if (map == MAP_FAILED)
return map;
@@ -107,9 +145,12 @@ void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
return map;
}
-int V4L2CameraProxy::munmap(void *addr, size_t length)
+int V4L2CameraProxy::munmap(V4L2CameraFile *file, void *addr, size_t length)
{
- LOG(V4L2Compat, Debug) << "Servicing munmap";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ MutexLocker locker(proxyMutex_);
auto iter = mmaps_.find(addr);
if (iter == mmaps_.end() || length != sizeimage_) {
@@ -137,31 +178,46 @@ bool V4L2CameraProxy::validateMemoryType(uint32_t memory)
return memory == V4L2_MEMORY_MMAP;
}
-void V4L2CameraProxy::setFmtFromConfig(StreamConfiguration &streamConfig)
-{
- curV4L2Format_.fmt.pix.width = streamConfig.size.width;
- curV4L2Format_.fmt.pix.height = streamConfig.size.height;
- curV4L2Format_.fmt.pix.pixelformat = drmToV4L2(streamConfig.pixelFormat);
- curV4L2Format_.fmt.pix.field = V4L2_FIELD_NONE;
- curV4L2Format_.fmt.pix.bytesperline =
- bplMultiplier(curV4L2Format_.fmt.pix.pixelformat) *
- curV4L2Format_.fmt.pix.width;
- curV4L2Format_.fmt.pix.sizeimage =
- imageSize(curV4L2Format_.fmt.pix.pixelformat,
- curV4L2Format_.fmt.pix.width,
- curV4L2Format_.fmt.pix.height);
- curV4L2Format_.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
-}
-
-unsigned int V4L2CameraProxy::calculateSizeImage(StreamConfiguration &streamConfig)
+void V4L2CameraProxy::setFmtFromConfig(const StreamConfiguration &streamConfig)
{
- /*
- * \todo Merge this method with setFmtFromConfig (need imageSize to
- * support all libcamera formats first, or filter out MJPEG for now).
- */
- return imageSize(drmToV4L2(streamConfig.pixelFormat),
- streamConfig.size.width,
- streamConfig.size.height);
+ const Size &size = streamConfig.size;
+
+ v4l2PixFormat_.width = size.width;
+ v4l2PixFormat_.height = size.height;
+ v4l2PixFormat_.pixelformat = V4L2PixelFormat::fromPixelFormat(streamConfig.pixelFormat)[0];
+ v4l2PixFormat_.field = V4L2_FIELD_NONE;
+ v4l2PixFormat_.bytesperline = streamConfig.stride;
+ v4l2PixFormat_.sizeimage = streamConfig.frameSize;
+ v4l2PixFormat_.colorspace = V4L2_COLORSPACE_SRGB;
+ v4l2PixFormat_.priv = V4L2_PIX_FMT_PRIV_MAGIC;
+ v4l2PixFormat_.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ v4l2PixFormat_.quantization = V4L2_QUANTIZATION_DEFAULT;
+ v4l2PixFormat_.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ sizeimage_ = streamConfig.frameSize;
+
+ const ControlInfoMap &controls = vcam_->controlInfo();
+ const auto &it = controls.find(&controls::FrameDurationLimits);
+
+ if (it != controls.end()) {
+ const int64_t duration = it->second.def().get<int64_t>();
+
+ v4l2TimePerFrame_.numerator = duration;
+ v4l2TimePerFrame_.denominator = 1000000;
+ } else {
+ /*
+ * Default to 30fps if the camera doesn't expose the
+ * FrameDurationLimits control.
+ *
+ * \todo Remove this once all pipeline handlers implement the
+ * control
+ */
+ LOG(V4L2Compat, Warning)
+ << "Camera does not support FrameDurationLimits";
+
+ v4l2TimePerFrame_.numerator = 333333;
+ v4l2TimePerFrame_.denominator = 1000000;
+ }
}
void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)
@@ -171,13 +227,15 @@ void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)
utils::strlcpy(reinterpret_cast<char *>(capabilities_.driver), driver.c_str(),
sizeof(capabilities_.driver));
- utils::strlcpy(reinterpret_cast<char *>(capabilities_.card), camera->name().c_str(),
+ utils::strlcpy(reinterpret_cast<char *>(capabilities_.card), camera->id().c_str(),
sizeof(capabilities_.card));
utils::strlcpy(reinterpret_cast<char *>(capabilities_.bus_info), bus_info.c_str(),
sizeof(capabilities_.bus_info));
/* \todo Put this in a header/config somewhere. */
capabilities_.version = KERNEL_VERSION(5, 2, 0);
- capabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ capabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE
+ | V4L2_CAP_STREAMING
+ | V4L2_CAP_EXT_PIX_FORMAT;
capabilities_.capabilities = capabilities_.device_caps
| V4L2_CAP_DEVICE_CAPS;
memset(capabilities_.reserved, 0, sizeof(capabilities_.reserved));
@@ -187,15 +245,19 @@ void V4L2CameraProxy::updateBuffers()
{
std::vector<V4L2Camera::Buffer> completedBuffers = vcam_->completedBuffers();
for (const V4L2Camera::Buffer &buffer : completedBuffers) {
- const FrameMetadata &fmd = buffer.data;
- struct v4l2_buffer &buf = buffers_[buffer.index];
+ const FrameMetadata &fmd = buffer.data_;
+ struct v4l2_buffer &buf = buffers_[buffer.index_];
switch (fmd.status) {
case FrameMetadata::FrameSuccess:
- buf.bytesused = fmd.planes[0].bytesused;
+ buf.bytesused = std::accumulate(fmd.planes().begin(),
+ fmd.planes().end(), 0,
+ [](unsigned int total, const auto &plane) {
+ return total + plane.bytesused;
+ });
buf.field = V4L2_FIELD_NONE;
buf.timestamp.tv_sec = fmd.timestamp / 1000000000;
- buf.timestamp.tv_usec = fmd.timestamp % 1000000;
+ buf.timestamp.tv_usec = (fmd.timestamp / 1000) % 1000000;
buf.sequence = fmd.sequence;
buf.flags |= V4L2_BUF_FLAG_DONE;
@@ -209,128 +271,237 @@ void V4L2CameraProxy::updateBuffers()
}
}
-int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)
+int V4L2CameraProxy::vidioc_querycap(V4L2CameraFile *file, struct v4l2_capability *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_querycap";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
*arg = capabilities_;
return 0;
}
-int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)
+int V4L2CameraProxy::vidioc_enum_framesizes(V4L2CameraFile *file, struct v4l2_frmsizeenum *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat(arg->pixel_format);
+ PixelFormat format = v4l2Format.toPixelFormat();
+ /*
+ * \todo This might need to be expanded as few pipeline handlers
+ * report StreamFormats.
+ */
+ const std::vector<Size> &frameSizes = streamConfig_.formats().sizes(format);
+
+ if (arg->index >= frameSizes.size())
+ return -EINVAL;
+
+ arg->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ arg->discrete.width = frameSizes[arg->index].width;
+ arg->discrete.height = frameSizes[arg->index].height;
+ memset(arg->reserved, 0, sizeof(arg->reserved));
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_enum_fmt(V4L2CameraFile *file, struct v4l2_fmtdesc *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
if (!validateBufferType(arg->type) ||
- arg->index > streamConfig_.formats().pixelformats().size())
+ arg->index >= streamConfig_.formats().pixelformats().size())
return -EINVAL;
- /* \todo Add map from format to description. */
- utils::strlcpy(reinterpret_cast<char *>(arg->description), "Video Format Description",
- sizeof(arg->description));
- arg->pixelformat = drmToV4L2(streamConfig_.formats().pixelformats()[arg->index]);
+ PixelFormat format = streamConfig_.formats().pixelformats()[arg->index];
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat::fromPixelFormat(format)[0];
+
+ arg->flags = format == formats::MJPEG ? V4L2_FMT_FLAG_COMPRESSED : 0;
+ utils::strlcpy(reinterpret_cast<char *>(arg->description),
+ v4l2Format.description(), sizeof(arg->description));
+ arg->pixelformat = v4l2Format;
+
+ memset(arg->reserved, 0, sizeof(arg->reserved));
return 0;
}
-int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_g_fmt(V4L2CameraFile *file, struct v4l2_format *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
if (!validateBufferType(arg->type))
return -EINVAL;
memset(&arg->fmt, 0, sizeof(arg->fmt));
- arg->fmt.pix = curV4L2Format_.fmt.pix;
+ arg->fmt.pix = v4l2PixFormat_;
return 0;
}
-void V4L2CameraProxy::tryFormat(struct v4l2_format *arg)
+int V4L2CameraProxy::tryFormat(struct v4l2_format *arg)
{
- PixelFormat format = v4l2ToDrm(arg->fmt.pix.pixelformat);
- const std::vector<PixelFormat> &formats =
- streamConfig_.formats().pixelformats();
- if (std::find(formats.begin(), formats.end(), format) == formats.end())
- format = streamConfig_.formats().pixelformats()[0];
-
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat(arg->fmt.pix.pixelformat);
+ PixelFormat format = v4l2Format.toPixelFormat();
Size size(arg->fmt.pix.width, arg->fmt.pix.height);
- const std::vector<Size> &sizes = streamConfig_.formats().sizes(format);
- if (std::find(sizes.begin(), sizes.end(), size) == sizes.end())
- size = streamConfig_.formats().sizes(format)[0];
- arg->fmt.pix.width = size.width;
- arg->fmt.pix.height = size.height;
- arg->fmt.pix.pixelformat = drmToV4L2(format);
+ StreamConfiguration config;
+ int ret = vcam_->validateConfiguration(format, size, &config);
+ if (ret < 0) {
+ LOG(V4L2Compat, Error)
+ << "Failed to negotiate a valid format: "
+ << format;
+ return -EINVAL;
+ }
+
+ arg->fmt.pix.width = config.size.width;
+ arg->fmt.pix.height = config.size.height;
+ arg->fmt.pix.pixelformat = V4L2PixelFormat::fromPixelFormat(config.pixelFormat)[0];
arg->fmt.pix.field = V4L2_FIELD_NONE;
- arg->fmt.pix.bytesperline = bplMultiplier(drmToV4L2(format)) *
- arg->fmt.pix.width;
- arg->fmt.pix.sizeimage = imageSize(drmToV4L2(format),
- arg->fmt.pix.width,
- arg->fmt.pix.height);
+ arg->fmt.pix.bytesperline = config.stride;
+ arg->fmt.pix.sizeimage = config.frameSize;
arg->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+ arg->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC;
+ arg->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ arg->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT;
+ arg->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ return 0;
}
-int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_s_fmt(V4L2CameraFile *file, struct v4l2_format *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
if (!validateBufferType(arg->type))
return -EINVAL;
- tryFormat(arg);
+ if (file->priority() < maxPriority())
+ return -EBUSY;
+
+ int ret = acquire(file);
+ if (ret < 0)
+ return ret;
+
+ ret = tryFormat(arg);
+ if (ret < 0)
+ return ret;
Size size(arg->fmt.pix.width, arg->fmt.pix.height);
- int ret = vcam_->configure(&streamConfig_, size,
- v4l2ToDrm(arg->fmt.pix.pixelformat),
- bufferCount_);
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat(arg->fmt.pix.pixelformat);
+ ret = vcam_->configure(&streamConfig_, size, v4l2Format.toPixelFormat(),
+ bufferCount_);
if (ret < 0)
return -EINVAL;
- unsigned int sizeimage = calculateSizeImage(streamConfig_);
- if (sizeimage == 0)
+ setFmtFromConfig(streamConfig_);
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_try_fmt(V4L2CameraFile *file, struct v4l2_format *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!validateBufferType(arg->type))
return -EINVAL;
- sizeimage_ = sizeimage;
+ int ret = tryFormat(arg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
- setFmtFromConfig(streamConfig_);
+enum v4l2_priority V4L2CameraProxy::maxPriority()
+{
+ auto max = std::max_element(files_.begin(), files_.end(),
+ [](const V4L2CameraFile *a, const V4L2CameraFile *b) {
+ return a->priority() < b->priority();
+ });
+ return max != files_.end() ? (*max)->priority() : V4L2_PRIORITY_UNSET;
+}
+
+int V4L2CameraProxy::vidioc_g_priority(V4L2CameraFile *file, enum v4l2_priority *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ *arg = maxPriority();
return 0;
}
-int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_s_priority(V4L2CameraFile *file, enum v4l2_priority *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
- if (!validateBufferType(arg->type))
+ if (*arg > V4L2_PRIORITY_RECORD)
return -EINVAL;
- tryFormat(arg);
+ if (file->priority() < maxPriority())
+ return -EBUSY;
+
+ file->setPriority(*arg);
return 0;
}
-int V4L2CameraProxy::freeBuffers()
+int V4L2CameraProxy::vidioc_enuminput(V4L2CameraFile *file, struct v4l2_input *arg)
{
- LOG(V4L2Compat, Debug) << "Freeing libcamera bufs";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
- int ret = vcam_->streamOff();
- if (ret < 0) {
- LOG(V4L2Compat, Error) << "Failed to stop stream";
- return ret;
- }
- vcam_->freeBuffers();
- bufferCount_ = 0;
+ if (arg->index != 0)
+ return -EINVAL;
+
+ memset(arg, 0, sizeof(*arg));
+
+ utils::strlcpy(reinterpret_cast<char *>(arg->name),
+ reinterpret_cast<char *>(capabilities_.card),
+ sizeof(arg->name));
+ arg->type = V4L2_INPUT_TYPE_CAMERA;
return 0;
}
-int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
+int V4L2CameraProxy::vidioc_g_input(V4L2CameraFile *file, int *arg)
{
- int ret;
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
- LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs";
+ *arg = 0;
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_s_input(V4L2CameraFile *file, int *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (*arg != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+void V4L2CameraProxy::freeBuffers()
+{
+ vcam_->freeBuffers();
+ buffers_.clear();
+ bufferCount_ = 0;
+}
+
+int V4L2CameraProxy::vidioc_reqbufs(V4L2CameraFile *file, struct v4l2_requestbuffers *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
if (!validateBufferType(arg->type) ||
!validateMemoryType(arg->memory))
@@ -338,20 +509,38 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
LOG(V4L2Compat, Debug) << arg->count << " buffers requested ";
+ if (file->priority() < maxPriority())
+ return -EBUSY;
+
+ if (!hasOwnership(file) && owner_)
+ return -EBUSY;
+
arg->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
+ arg->flags = 0;
+ memset(arg->reserved, 0, sizeof(arg->reserved));
- if (arg->count == 0)
- return freeBuffers();
+ if (arg->count == 0) {
+ /* \todo Add buffer orphaning support */
+ if (!mmaps_.empty())
+ return -EBUSY;
- Size size(curV4L2Format_.fmt.pix.width, curV4L2Format_.fmt.pix.height);
- ret = vcam_->configure(&streamConfig_, size,
- v4l2ToDrm(curV4L2Format_.fmt.pix.pixelformat),
- arg->count);
- if (ret < 0)
- return -EINVAL;
+ if (vcam_->isRunning())
+ return -EBUSY;
+
+ freeBuffers();
+ release(file);
- sizeimage_ = calculateSizeImage(streamConfig_);
- if (sizeimage_ == 0)
+ return 0;
+ }
+
+ if (bufferCount_ > 0)
+ freeBuffers();
+
+ Size size(v4l2PixFormat_.width, v4l2PixFormat_.height);
+ V4L2PixelFormat v4l2Format = V4L2PixelFormat(v4l2PixFormat_.pixelformat);
+ int ret = vcam_->configure(&streamConfig_, size,
+ v4l2Format.toPixelFormat(), arg->count);
+ if (ret < 0)
return -EINVAL;
setFmtFromConfig(streamConfig_);
@@ -369,22 +558,29 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
for (unsigned int i = 0; i < arg->count; i++) {
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.length = curV4L2Format_.fmt.pix.sizeimage;
+ buf.length = v4l2PixFormat_.sizeimage;
buf.memory = V4L2_MEMORY_MMAP;
- buf.m.offset = i * curV4L2Format_.fmt.pix.sizeimage;
+ buf.m.offset = i * v4l2PixFormat_.sizeimage;
buf.index = i;
+ buf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
buffers_[i] = buf;
}
LOG(V4L2Compat, Debug) << "Allocated " << arg->count << " buffers";
+ acquire(file);
+
return 0;
}
-int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (arg->index >= bufferCount_)
+ return -EINVAL;
if (!validateBufferType(arg->type) ||
arg->index >= bufferCount_)
@@ -397,10 +593,52 @@ int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
return 0;
}
-int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_prepare_buf(V4L2CameraFile *file, struct v4l2_buffer *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_qbuf, index = "
- << arg->index;
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__
+ << "(index=" << arg->index << ")";
+
+ if (!hasOwnership(file))
+ return -EBUSY;
+
+ if (arg->index >= bufferCount_)
+ return -EINVAL;
+
+ if (arg->flags & V4L2_BUF_FLAG_REQUEST_FD)
+ return -EINVAL;
+
+ if (!validateBufferType(arg->type) ||
+ !validateMemoryType(arg->memory))
+ return -EINVAL;
+
+ struct v4l2_buffer &buffer = buffers_[arg->index];
+
+ if (buffer.flags & V4L2_BUF_FLAG_QUEUED ||
+ buffer.flags & V4L2_BUF_FLAG_PREPARED)
+ return -EINVAL;
+
+ buffer.flags |= V4L2_BUF_FLAG_PREPARED;
+
+ arg->flags = buffer.flags;
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__
+ << "(index=" << arg->index << ")";
+
+ if (arg->index >= bufferCount_)
+ return -EINVAL;
+
+ if (buffers_[arg->index].flags & V4L2_BUF_FLAG_QUEUED)
+ return -EINVAL;
+
+ if (!hasOwnership(file))
+ return -EBUSY;
if (!validateBufferType(arg->type) ||
!validateMemoryType(arg->memory) ||
@@ -411,55 +649,130 @@ int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
if (ret < 0)
return ret;
- arg->flags |= V4L2_BUF_FLAG_QUEUED;
- arg->flags &= ~V4L2_BUF_FLAG_DONE;
+ buffers_[arg->index].flags |= V4L2_BUF_FLAG_QUEUED;
+
+ arg->flags = buffers_[arg->index].flags;
return ret;
}
-int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg,
+ Mutex *lock)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (arg->index >= bufferCount_)
+ return -EINVAL;
+
+ if (!hasOwnership(file))
+ return -EBUSY;
+
+ if (!vcam_->isRunning())
+ return -EINVAL;
if (!validateBufferType(arg->type) ||
!validateMemoryType(arg->memory))
return -EINVAL;
- if (nonBlocking_ && !vcam_->bufferSema_.tryAcquire())
+ if (!file->nonBlocking()) {
+ lock->unlock();
+ vcam_->waitForBufferAvailable();
+ lock->lock();
+ } else if (!vcam_->isBufferAvailable())
return -EAGAIN;
- else
- vcam_->bufferSema_.acquire();
+
+ /*
+ * We need to check here again in case stream was turned off while we
+ * were blocked on waitForBufferAvailable().
+ */
+ if (!vcam_->isRunning())
+ return -EINVAL;
updateBuffers();
struct v4l2_buffer &buf = buffers_[currentBuf_];
- buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
+ buf.flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_PREPARED);
buf.length = sizeimage_;
*arg = buf;
currentBuf_ = (currentBuf_ + 1) % bufferCount_;
+ uint64_t data;
+ int ret = ::read(file->efd(), &data, sizeof(data));
+ if (ret != sizeof(data))
+ LOG(V4L2Compat, Error) << "Failed to clear eventfd POLLIN";
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_expbuf(V4L2CameraFile *file, struct v4l2_exportbuffer *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!hasOwnership(file))
+ return -EBUSY;
+
+ /* \todo Verify that the memory type is MMAP when adding DMABUF support */
+ if (!validateBufferType(arg->type))
+ return -EINVAL;
+
+ if (arg->index >= bufferCount_)
+ return -EINVAL;
+
+ if (arg->flags & ~(O_CLOEXEC | O_ACCMODE))
+ return -EINVAL;
+
+ memset(arg->reserved, 0, sizeof(arg->reserved));
+
+ /* \todo honor the O_ACCMODE flags passed to this function */
+ arg->fd = fcntl(vcam_->getBufferFd(arg->index),
+ arg->flags & O_CLOEXEC ? F_DUPFD_CLOEXEC : F_DUPFD, 0);
+
return 0;
}
-int V4L2CameraProxy::vidioc_streamon(int *arg)
+int V4L2CameraProxy::vidioc_streamon(V4L2CameraFile *file, int *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (bufferCount_ == 0)
+ return -EINVAL;
if (!validateBufferType(*arg))
return -EINVAL;
+ if (file->priority() < maxPriority())
+ return -EBUSY;
+
+ if (!hasOwnership(file))
+ return -EBUSY;
+
+ if (vcam_->isRunning())
+ return 0;
+
+ currentBuf_ = 0;
+
return vcam_->streamOn();
}
-int V4L2CameraProxy::vidioc_streamoff(int *arg)
+int V4L2CameraProxy::vidioc_streamoff(V4L2CameraFile *file, int *arg)
{
- LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff";
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
if (!validateBufferType(*arg))
return -EINVAL;
+ if (file->priority() < maxPriority())
+ return -EBUSY;
+
+ if (!hasOwnership(file) && owner_)
+ return -EBUSY;
+
int ret = vcam_->streamOff();
for (struct v4l2_buffer &buf : buffers_)
@@ -468,42 +781,169 @@ int V4L2CameraProxy::vidioc_streamoff(int *arg)
return ret;
}
-int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
+int V4L2CameraProxy::vidioc_g_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!validateBufferType(arg->type))
+ return -EINVAL;
+
+ memset(&arg->parm, 0, sizeof(arg->parm));
+
+ arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ arg->parm.capture.timeperframe = v4l2TimePerFrame_;
+
+ return 0;
+}
+
+int V4L2CameraProxy::vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg)
{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!validateBufferType(arg->type))
+ return -EINVAL;
+
+ /*
+ * Store the frame duration if it is valid, otherwise keep the current
+ * value.
+ *
+ * \todo The provided value should be adjusted based on the camera
+ * capabilities.
+ */
+ if (arg->parm.capture.timeperframe.numerator &&
+ arg->parm.capture.timeperframe.denominator)
+ v4l2TimePerFrame_ = arg->parm.capture.timeperframe;
+
+ memset(&arg->parm, 0, sizeof(arg->parm));
+
+ arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ arg->parm.capture.timeperframe = v4l2TimePerFrame_;
+
+ /* Apply the frame duration. */
+ utils::Duration frameDuration = 1.0s * v4l2TimePerFrame_.numerator
+ / v4l2TimePerFrame_.denominator;
+ int64_t uDuration = frameDuration.get<std::micro>();
+ vcam_->controls().set(controls::FrameDurationLimits, { uDuration, uDuration });
+
+ return 0;
+}
+
+const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
+ VIDIOC_QUERYCAP,
+ VIDIOC_ENUM_FRAMESIZES,
+ VIDIOC_ENUM_FMT,
+ VIDIOC_G_FMT,
+ VIDIOC_S_FMT,
+ VIDIOC_TRY_FMT,
+ VIDIOC_G_PRIORITY,
+ VIDIOC_S_PRIORITY,
+ VIDIOC_ENUMINPUT,
+ VIDIOC_G_INPUT,
+ VIDIOC_S_INPUT,
+ VIDIOC_REQBUFS,
+ VIDIOC_QUERYBUF,
+ VIDIOC_PREPARE_BUF,
+ VIDIOC_QBUF,
+ VIDIOC_DQBUF,
+ VIDIOC_EXPBUF,
+ VIDIOC_STREAMON,
+ VIDIOC_STREAMOFF,
+ VIDIOC_G_PARM,
+ VIDIOC_S_PARM,
+};
+
+int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg)
+{
+ MutexLocker locker(proxyMutex_);
+
+ /*
+ * The Linux Kernel only processes 32 bits of an IOCTL.
+ *
+ * Prevent unexpected sign-extensions that could occur if applications
+ * use a signed int for the ioctl request, which would sign-extend to
+ * an incorrect value for unsigned longs on 64 bit architectures by
+ * explicitly casting as an unsigned int here.
+ */
+ unsigned int request = longRequest;
+
+ if (!arg && (_IOC_DIR(request) & _IOC_WRITE)) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ if (!arg && (_IOC_DIR(request) & _IOC_READ)) {
+ errno = EFAULT;
+ return -1;
+ }
+
int ret;
switch (request) {
case VIDIOC_QUERYCAP:
- ret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));
+ ret = vidioc_querycap(file, static_cast<struct v4l2_capability *>(arg));
+ break;
+ case VIDIOC_ENUM_FRAMESIZES:
+ ret = vidioc_enum_framesizes(file, static_cast<struct v4l2_frmsizeenum *>(arg));
break;
case VIDIOC_ENUM_FMT:
- ret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));
+ ret = vidioc_enum_fmt(file, static_cast<struct v4l2_fmtdesc *>(arg));
break;
case VIDIOC_G_FMT:
- ret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));
+ ret = vidioc_g_fmt(file, static_cast<struct v4l2_format *>(arg));
break;
case VIDIOC_S_FMT:
- ret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));
+ ret = vidioc_s_fmt(file, static_cast<struct v4l2_format *>(arg));
break;
case VIDIOC_TRY_FMT:
- ret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));
+ ret = vidioc_try_fmt(file, static_cast<struct v4l2_format *>(arg));
+ break;
+ case VIDIOC_G_PRIORITY:
+ ret = vidioc_g_priority(file, static_cast<enum v4l2_priority *>(arg));
+ break;
+ case VIDIOC_S_PRIORITY:
+ ret = vidioc_s_priority(file, static_cast<enum v4l2_priority *>(arg));
+ break;
+ case VIDIOC_ENUMINPUT:
+ ret = vidioc_enuminput(file, static_cast<struct v4l2_input *>(arg));
+ break;
+ case VIDIOC_G_INPUT:
+ ret = vidioc_g_input(file, static_cast<int *>(arg));
+ break;
+ case VIDIOC_S_INPUT:
+ ret = vidioc_s_input(file, static_cast<int *>(arg));
break;
case VIDIOC_REQBUFS:
- ret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));
+ ret = vidioc_reqbufs(file, static_cast<struct v4l2_requestbuffers *>(arg));
break;
case VIDIOC_QUERYBUF:
- ret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));
+ ret = vidioc_querybuf(file, static_cast<struct v4l2_buffer *>(arg));
break;
case VIDIOC_QBUF:
- ret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));
+ ret = vidioc_qbuf(file, static_cast<struct v4l2_buffer *>(arg));
break;
case VIDIOC_DQBUF:
- ret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));
+ ret = vidioc_dqbuf(file, static_cast<struct v4l2_buffer *>(arg), &proxyMutex_);
+ break;
+ case VIDIOC_EXPBUF:
+ ret = vidioc_expbuf(file, static_cast<struct v4l2_exportbuffer *>(arg));
break;
case VIDIOC_STREAMON:
- ret = vidioc_streamon(static_cast<int *>(arg));
+ ret = vidioc_streamon(file, static_cast<int *>(arg));
break;
case VIDIOC_STREAMOFF:
- ret = vidioc_streamoff(static_cast<int *>(arg));
+ ret = vidioc_streamoff(file, static_cast<int *>(arg));
+ break;
+ case VIDIOC_G_PARM:
+ ret = vidioc_g_parm(file, static_cast<struct v4l2_streamparm *>(arg));
+ break;
+ case VIDIOC_S_PARM:
+ ret = vidioc_s_parm(file, static_cast<struct v4l2_streamparm *>(arg));
break;
default:
ret = -ENOTTY;
@@ -518,94 +958,43 @@ int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
return ret;
}
-struct PixelFormatPlaneInfo {
- unsigned int bitsPerPixel;
- unsigned int hSubSampling;
- unsigned int vSubSampling;
-};
-
-struct PixelFormatInfo {
- PixelFormat format;
- uint32_t v4l2Format;
- unsigned int numPlanes;
- std::array<PixelFormatPlaneInfo, 3> planes;
-};
-
-namespace {
-
-static const std::array<PixelFormatInfo, 13> pixelFormatInfo = {{
- /* RGB formats. */
- { PixelFormat(DRM_FORMAT_RGB888), V4L2_PIX_FMT_BGR24, 1, {{ { 24, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_BGR888), V4L2_PIX_FMT_RGB24, 1, {{ { 24, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_BGRA8888), V4L2_PIX_FMT_ARGB32, 1, {{ { 32, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- /* YUV packed formats. */
- { PixelFormat(DRM_FORMAT_UYVY), V4L2_PIX_FMT_UYVY, 1, {{ { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_VYUY), V4L2_PIX_FMT_VYUY, 1, {{ { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_YUYV), V4L2_PIX_FMT_YUYV, 1, {{ { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_YVYU), V4L2_PIX_FMT_YVYU, 1, {{ { 16, 1, 1 }, { 0, 0, 0 }, { 0, 0, 0 } }} },
- /* YUY planar formats. */
- { PixelFormat(DRM_FORMAT_NV12), V4L2_PIX_FMT_NV12, 2, {{ { 8, 1, 1 }, { 16, 2, 2 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_NV21), V4L2_PIX_FMT_NV21, 2, {{ { 8, 1, 1 }, { 16, 2, 2 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_NV16), V4L2_PIX_FMT_NV16, 2, {{ { 8, 1, 1 }, { 16, 2, 1 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_NV61), V4L2_PIX_FMT_NV61, 2, {{ { 8, 1, 1 }, { 16, 2, 1 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_NV24), V4L2_PIX_FMT_NV24, 2, {{ { 8, 1, 1 }, { 16, 2, 1 }, { 0, 0, 0 } }} },
- { PixelFormat(DRM_FORMAT_NV42), V4L2_PIX_FMT_NV42, 2, {{ { 8, 1, 1 }, { 16, 1, 1 }, { 0, 0, 0 } }} },
-}};
-
-} /* namespace */
-
-/* \todo make libcamera export these */
-unsigned int V4L2CameraProxy::bplMultiplier(uint32_t format)
-{
- auto info = std::find_if(pixelFormatInfo.begin(), pixelFormatInfo.end(),
- [format](const PixelFormatInfo &info) {
- return info.v4l2Format == format;
- });
- if (info == pixelFormatInfo.end())
- return 0;
-
- return info->planes[0].bitsPerPixel / 8;
+bool V4L2CameraProxy::hasOwnership(V4L2CameraFile *file)
+{
+ return owner_ == file;
}
-unsigned int V4L2CameraProxy::imageSize(uint32_t format, unsigned int width,
- unsigned int height)
+/**
+ * \brief Acquire exclusive ownership of the V4L2Camera
+ *
+ * \return Zero on success or if already acquired, and negative error on
+ * failure.
+ *
+ * This is sufficient for poll()ing for buffers. Events, however, are signaled
+ * on the file level, so all fds must be signaled. poll()ing from a different
+ * fd than the one that locks the device is a corner case, and is currently not
+ * supported.
+ */
+int V4L2CameraProxy::acquire(V4L2CameraFile *file)
{
- auto info = std::find_if(pixelFormatInfo.begin(), pixelFormatInfo.end(),
- [format](const PixelFormatInfo &info) {
- return info.v4l2Format == format;
- });
- if (info == pixelFormatInfo.end())
+ if (owner_ == file)
return 0;
- unsigned int multiplier = 0;
- for (unsigned int i = 0; i < info->numPlanes; ++i)
- multiplier += info->planes[i].bitsPerPixel
- / info->planes[i].hSubSampling
- / info->planes[i].vSubSampling;
+ if (owner_)
+ return -EBUSY;
- return width * height * multiplier / 8;
-}
+ vcam_->bind(file->efd());
-PixelFormat V4L2CameraProxy::v4l2ToDrm(uint32_t format)
-{
- auto info = std::find_if(pixelFormatInfo.begin(), pixelFormatInfo.end(),
- [format](const PixelFormatInfo &info) {
- return info.v4l2Format == format;
- });
- if (info == pixelFormatInfo.end())
- return PixelFormat();
+ owner_ = file;
- return info->format;
+ return 0;
}
-uint32_t V4L2CameraProxy::drmToV4L2(const PixelFormat &format)
+void V4L2CameraProxy::release(V4L2CameraFile *file)
{
- auto info = std::find_if(pixelFormatInfo.begin(), pixelFormatInfo.end(),
- [format](const PixelFormatInfo &info) {
- return info.format == format;
- });
- if (info == pixelFormatInfo.end())
- return format;
+ if (owner_ != file)
+ return;
+
+ vcam_->unbind();
- return info->v4l2Format;
+ owner_ = nullptr;
}
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index e15b230d..5aa352c3 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -1,82 +1,111 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera
+ * Proxy to V4L2 compatibility camera
*/
-#ifndef __V4L2_CAMERA_PROXY_H__
-#define __V4L2_CAMERA_PROXY_H__
+#pragma once
#include <linux/videodev2.h>
#include <map>
#include <memory>
+#include <set>
#include <sys/types.h>
#include <vector>
+#include <libcamera/base/mutex.h>
+
#include <libcamera/camera.h>
#include "v4l2_camera.h"
-using namespace libcamera;
+class V4L2CameraFile;
class V4L2CameraProxy
{
public:
- V4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);
+ V4L2CameraProxy(unsigned int index, std::shared_ptr<libcamera::Camera> camera);
- int open(bool nonBlocking);
- void dup();
- void close();
- void *mmap(void *addr, size_t length, int prot, int flags, off_t offset);
- int munmap(void *addr, size_t length);
+ int open(V4L2CameraFile *file) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
+ void close(V4L2CameraFile *file) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
+ void *mmap(V4L2CameraFile *file, void *addr, size_t length, int prot,
+ int flags, off64_t offset) LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
+ int munmap(V4L2CameraFile *file, void *addr, size_t length)
+ LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
- int ioctl(unsigned long request, void *arg);
+ int ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
+ LIBCAMERA_TSA_EXCLUDES(proxyMutex_);
private:
bool validateBufferType(uint32_t type);
bool validateMemoryType(uint32_t memory);
- void setFmtFromConfig(StreamConfiguration &streamConfig);
- unsigned int calculateSizeImage(StreamConfiguration &streamConfig);
- void querycap(std::shared_ptr<Camera> camera);
- void tryFormat(struct v4l2_format *arg);
+ void setFmtFromConfig(const libcamera::StreamConfiguration &streamConfig);
+ void querycap(std::shared_ptr<libcamera::Camera> camera);
+ int tryFormat(struct v4l2_format *arg);
+ enum v4l2_priority maxPriority();
void updateBuffers();
- int freeBuffers();
-
- int vidioc_querycap(struct v4l2_capability *arg);
- int vidioc_enum_fmt(struct v4l2_fmtdesc *arg);
- int vidioc_g_fmt(struct v4l2_format *arg);
- int vidioc_s_fmt(struct v4l2_format *arg);
- int vidioc_try_fmt(struct v4l2_format *arg);
- int vidioc_reqbufs(struct v4l2_requestbuffers *arg);
- int vidioc_querybuf(struct v4l2_buffer *arg);
- int vidioc_qbuf(struct v4l2_buffer *arg);
- int vidioc_dqbuf(struct v4l2_buffer *arg);
- int vidioc_streamon(int *arg);
- int vidioc_streamoff(int *arg);
-
- static unsigned int bplMultiplier(uint32_t format);
- static unsigned int imageSize(uint32_t format, unsigned int width,
- unsigned int height);
-
- static PixelFormat v4l2ToDrm(uint32_t format);
- static uint32_t drmToV4L2(const PixelFormat &format);
+ void freeBuffers();
+
+ int vidioc_querycap(V4L2CameraFile *file, struct v4l2_capability *arg);
+ int vidioc_enum_framesizes(V4L2CameraFile *file, struct v4l2_frmsizeenum *arg);
+ int vidioc_enum_fmt(V4L2CameraFile *file, struct v4l2_fmtdesc *arg);
+ int vidioc_g_fmt(V4L2CameraFile *file, struct v4l2_format *arg);
+ int vidioc_s_fmt(V4L2CameraFile *file, struct v4l2_format *arg);
+ int vidioc_try_fmt(V4L2CameraFile *file, struct v4l2_format *arg);
+ int vidioc_g_priority(V4L2CameraFile *file, enum v4l2_priority *arg);
+ int vidioc_s_priority(V4L2CameraFile *file, enum v4l2_priority *arg);
+ int vidioc_enuminput(V4L2CameraFile *file, struct v4l2_input *arg);
+ int vidioc_g_input(V4L2CameraFile *file, int *arg);
+ int vidioc_s_input(V4L2CameraFile *file, int *arg);
+ int vidioc_reqbufs(V4L2CameraFile *file, struct v4l2_requestbuffers *arg);
+ int vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
+ int vidioc_prepare_buf(V4L2CameraFile *file, struct v4l2_buffer *arg);
+ int vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
+ int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg,
+ libcamera::Mutex *lock) LIBCAMERA_TSA_REQUIRES(*lock);
+ int vidioc_expbuf(V4L2CameraFile *file, struct v4l2_exportbuffer *arg);
+ int vidioc_streamon(V4L2CameraFile *file, int *arg);
+ int vidioc_streamoff(V4L2CameraFile *file, int *arg);
+ int vidioc_g_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg);
+ int vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg);
+
+ bool hasOwnership(V4L2CameraFile *file);
+ int acquire(V4L2CameraFile *file);
+ void release(V4L2CameraFile *file);
+
+ static const std::set<unsigned long> supportedIoctls_;
unsigned int refcount_;
unsigned int index_;
- bool nonBlocking_;
- struct v4l2_format curV4L2Format_;
- StreamConfiguration streamConfig_;
- struct v4l2_capability capabilities_;
+ libcamera::StreamConfiguration streamConfig_;
unsigned int bufferCount_;
unsigned int currentBuf_;
unsigned int sizeimage_;
+ struct v4l2_capability capabilities_;
+ struct v4l2_pix_format v4l2PixFormat_;
+ struct v4l2_fract v4l2TimePerFrame_;
+
std::vector<struct v4l2_buffer> buffers_;
std::map<void *, unsigned int> mmaps_;
+ std::set<V4L2CameraFile *> files_;
+
std::unique_ptr<V4L2Camera> vcam_;
-};
-#endif /* __V4L2_CAMERA_PROXY_H__ */
+ /*
+ * This is the exclusive owner of this V4L2CameraProxy instance.
+ * When there is no owner, anybody can call any ioctl before reqbufs.
+ * The first file to call reqbufs with count > 0 or s_fmt will become
+ * the owner, and when the owner calls reqbufs with count = 0 it will
+ * release ownership. Any buffer-related ioctl (except querybuf) or
+ * s_fmt that is called by a non-owner while there exists an owner
+ * will return -EBUSY.
+ */
+ V4L2CameraFile *owner_;
+
+ /* This mutex is to serialize access to the proxy. */
+ libcamera::Mutex proxyMutex_;
+};
diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp
index a162037f..ff833f57 100644
--- a/src/v4l2/v4l2_compat.cpp
+++ b/src/v4l2/v4l2_compat.cpp
@@ -1,18 +1,22 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_compat.cpp - V4L2 compatibility layer
+ * V4L2 compatibility layer
*/
#include "v4l2_compat_manager.h"
-#include <errno.h>
+#include <assert.h>
#include <fcntl.h>
#include <stdarg.h>
+#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/base/utils.h>
#define LIBCAMERA_PUBLIC __attribute__((visibility("default")))
@@ -26,38 +30,98 @@ using namespace libcamera;
va_end(ap); \
}
+namespace {
+
+/*
+ * Determine if the flags require a further mode arguments that needs to be
+ * parsed from va_args.
+ */
+bool needs_mode(int flags)
+{
+ return (flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
+}
+
+} /* namespace */
+
extern "C" {
LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
oflag, mode);
}
-/* _FORTIFY_SOURCE redirects open to __open_2 */
-LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
+#ifndef open64
+LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...)
{
- return open(path, oflag);
+ mode_t mode = 0;
+ if (needs_mode(oflag))
+ extract_va_arg(mode_t, mode, oflag);
+
+ return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
+ oflag | O_LARGEFILE, mode);
}
+#endif
LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);
}
+#ifndef openat64
+LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...)
+{
+ mode_t mode = 0;
+ if (needs_mode(oflag))
+ extract_va_arg(mode_t, mode, oflag);
+
+ return V4L2CompatManager::instance()->openat(dirfd, path,
+ oflag | O_LARGEFILE, mode);
+}
+#endif
+
+/*
+ * _FORTIFY_SOURCE redirects open* to __open*_2. Disable the
+ * -Wmissing-declarations warnings, as the functions won't be declared if
+ * _FORTIFY_SOURCE is not in use.
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-declarations"
+
+LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return open(path, oflag);
+}
+
+LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return open64(path, oflag);
+}
+
LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
{
+ assert(!needs_mode(oflag));
return openat(dirfd, path, oflag);
}
+LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return openat64(dirfd, path, oflag);
+}
+
+#pragma GCC diagnostic pop
+
LIBCAMERA_PUBLIC int dup(int oldfd)
{
return V4L2CompatManager::instance()->dup(oldfd);
@@ -75,12 +139,25 @@ LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,
fd, offset);
}
+#ifndef mmap64
+LIBCAMERA_PUBLIC void *mmap64(void *addr, size_t length, int prot, int flags,
+ int fd, off64_t offset)
+{
+ return V4L2CompatManager::instance()->mmap(addr, length, prot, flags,
+ fd, offset);
+}
+#endif
+
LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)
{
return V4L2CompatManager::instance()->munmap(addr, length);
}
+#if HAVE_POSIX_IOCTL
+LIBCAMERA_PUBLIC int ioctl(int fd, int request, ...)
+#else
LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)
+#endif
{
void *arg;
extract_va_arg(void *, arg, request);
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 961d06b3..f53fb300 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -1,8 +1,8 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_compat_manager.cpp - V4L2 compatibility manager
+ * V4L2 compatibility manager
*/
#include "v4l2_compat_manager.h"
@@ -10,7 +10,6 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <map>
-#include <stdarg.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
@@ -19,10 +18,14 @@
#include <sys/types.h>
#include <unistd.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
+#include <libcamera/property_ids.h>
-#include "log.h"
+#include "v4l2_camera_file.h"
using namespace libcamera;
@@ -39,17 +42,17 @@ void get_symbol(T &func, const char *name)
V4L2CompatManager::V4L2CompatManager()
: cm_(nullptr)
{
- get_symbol(fops_.openat, "openat");
+ get_symbol(fops_.openat, "openat64");
get_symbol(fops_.dup, "dup");
get_symbol(fops_.close, "close");
get_symbol(fops_.ioctl, "ioctl");
- get_symbol(fops_.mmap, "mmap");
+ get_symbol(fops_.mmap, "mmap64");
get_symbol(fops_.munmap, "munmap");
}
V4L2CompatManager::~V4L2CompatManager()
{
- devices_.clear();
+ files_.clear();
mmaps_.clear();
if (cm_) {
@@ -79,11 +82,10 @@ int V4L2CompatManager::start()
* For each Camera registered in the system, a V4L2CameraProxy gets
* created here to wrap a camera device.
*/
- unsigned int index = 0;
- for (auto &camera : cm_->cameras()) {
+ auto cameras = cm_->cameras();
+ for (auto [index, camera] : utils::enumerate(cameras)) {
V4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);
proxies_.emplace_back(proxy);
- ++index;
}
return 0;
@@ -95,13 +97,13 @@ V4L2CompatManager *V4L2CompatManager::instance()
return &instance;
}
-V4L2CameraProxy *V4L2CompatManager::getProxy(int fd)
+std::shared_ptr<V4L2CameraFile> V4L2CompatManager::cameraFile(int fd)
{
- auto device = devices_.find(fd);
- if (device == devices_.end())
+ auto file = files_.find(fd);
+ if (file == files_.end())
return nullptr;
- return device->second;
+ return file->second;
}
int V4L2CompatManager::getCameraIndex(int fd)
@@ -111,15 +113,35 @@ int V4L2CompatManager::getCameraIndex(int fd)
if (ret < 0)
return -1;
- std::shared_ptr<Camera> target = cm_->get(statbuf.st_rdev);
- if (!target)
- return -1;
+ const dev_t devnum = statbuf.st_rdev;
- unsigned int index = 0;
- for (auto &camera : cm_->cameras()) {
- if (camera == target)
- return index;
- ++index;
+ /*
+ * Iterate each known camera and identify if it reports this nodes
+ * device number in its list of SystemDevices.
+ */
+ auto cameras = cm_->cameras();
+ for (auto [index, camera] : utils::enumerate(cameras)) {
+ Span<const int64_t> devices = camera->properties()
+ .get(properties::SystemDevices)
+ .value_or(Span<int64_t>{});
+
+ /*
+ * While there may be multiple cameras that could reference the
+ * same device node, we take a first match as a best effort for
+ * now.
+ *
+ * \todo Each camera can be accessed through any of the video
+ * device nodes that it uses. This may confuse applications.
+ * Consider reworking the V4L2 adaptation layer to instead
+ * expose each Camera instance through a single video device
+ * node (with a consistent and stable mapping). The other
+ * device nodes could possibly be hidden from the application
+ * by intercepting additional calls to the C library.
+ */
+ for (const int64_t dev : devices) {
+ if (dev == static_cast<int64_t>(devnum))
+ return index;
+ }
}
return -1;
@@ -142,27 +164,24 @@ int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mod
ret = getCameraIndex(fd);
if (ret < 0) {
- LOG(V4L2Compat, Info) << "No camera found for " << path;
+ LOG(V4L2Compat, Debug) << "No camera found for " << path;
return fd;
}
fops_.close(fd);
- unsigned int camera_index = static_cast<unsigned int>(ret);
-
- V4L2CameraProxy *proxy = proxies_[camera_index].get();
- ret = proxy->open(oflag & O_NONBLOCK);
- if (ret < 0)
- return ret;
-
- int efd = eventfd(0, oflag & (O_CLOEXEC | O_NONBLOCK));
- if (efd < 0) {
- proxy->close();
+ int efd = eventfd(0, EFD_SEMAPHORE |
+ ((oflag & O_CLOEXEC) ? EFD_CLOEXEC : 0) |
+ ((oflag & O_NONBLOCK) ? EFD_NONBLOCK : 0));
+ if (efd < 0)
return efd;
- }
- devices_.emplace(efd, proxy);
+ V4L2CameraProxy *proxy = proxies_[ret].get();
+ files_.emplace(efd, std::make_shared<V4L2CameraFile>(dirfd, path, efd,
+ oflag & O_NONBLOCK,
+ proxy));
+ LOG(V4L2Compat, Debug) << "Opened " << path << " -> fd " << efd;
return efd;
}
@@ -172,40 +191,36 @@ int V4L2CompatManager::dup(int oldfd)
if (newfd < 0)
return newfd;
- auto device = devices_.find(oldfd);
- if (device != devices_.end()) {
- V4L2CameraProxy *proxy = device->second;
- devices_[newfd] = proxy;
- proxy->dup();
- }
+ auto file = files_.find(oldfd);
+ if (file != files_.end())
+ files_[newfd] = file->second;
return newfd;
}
int V4L2CompatManager::close(int fd)
{
- V4L2CameraProxy *proxy = getProxy(fd);
- if (proxy) {
- proxy->close();
- devices_.erase(fd);
- return 0;
- }
+ auto file = files_.find(fd);
+ if (file != files_.end())
+ files_.erase(file);
+ /* We still need to close the eventfd. */
return fops_.close(fd);
}
void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,
- int fd, off_t offset)
+ int fd, off64_t offset)
{
- V4L2CameraProxy *proxy = getProxy(fd);
- if (!proxy)
+ std::shared_ptr<V4L2CameraFile> file = cameraFile(fd);
+ if (!file)
return fops_.mmap(addr, length, prot, flags, fd, offset);
- void *map = proxy->mmap(addr, length, prot, flags, offset);
+ void *map = file->proxy()->mmap(file.get(), addr, length, prot, flags,
+ offset);
if (map == MAP_FAILED)
return map;
- mmaps_[map] = proxy;
+ mmaps_[map] = file;
return map;
}
@@ -215,9 +230,9 @@ int V4L2CompatManager::munmap(void *addr, size_t length)
if (device == mmaps_.end())
return fops_.munmap(addr, length);
- V4L2CameraProxy *proxy = device->second;
+ V4L2CameraFile *file = device->second.get();
- int ret = proxy->munmap(addr, length);
+ int ret = file->proxy()->munmap(file, addr, length);
if (ret < 0)
return ret;
@@ -228,9 +243,9 @@ int V4L2CompatManager::munmap(void *addr, size_t length)
int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)
{
- V4L2CameraProxy *proxy = getProxy(fd);
- if (!proxy)
+ std::shared_ptr<V4L2CameraFile> file = cameraFile(fd);
+ if (!file)
return fops_.ioctl(fd, request, arg);
- return proxy->ioctl(request, arg);
+ return file->proxy()->ioctl(file.get(), request, arg);
}
diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h
index 872c7c3b..f7c6f122 100644
--- a/src/v4l2/v4l2_compat_manager.h
+++ b/src/v4l2/v4l2_compat_manager.h
@@ -1,12 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
- * v4l2_compat_manager.h - V4L2 compatibility manager
+ * V4L2 compatibility manager
*/
-#ifndef __V4L2_COMPAT_MANAGER_H__
-#define __V4L2_COMPAT_MANAGER_H__
+#pragma once
#include <fcntl.h>
#include <map>
@@ -18,8 +17,6 @@
#include "v4l2_camera_proxy.h"
-using namespace libcamera;
-
class V4L2CompatManager
{
public:
@@ -30,7 +27,7 @@ public:
using close_func_t = int (*)(int fd);
using ioctl_func_t = int (*)(int fd, unsigned long request, ...);
using mmap_func_t = void *(*)(void *addr, size_t length, int prot,
- int flags, int fd, off_t offset);
+ int flags, int fd, off64_t offset);
using munmap_func_t = int (*)(void *addr, size_t length);
openat_func_t openat;
@@ -43,7 +40,6 @@ public:
static V4L2CompatManager *instance();
- V4L2CameraProxy *getProxy(int fd);
const FileOperations &fops() const { return fops_; }
int openat(int dirfd, const char *path, int oflag, mode_t mode);
@@ -51,7 +47,7 @@ public:
int dup(int oldfd);
int close(int fd);
void *mmap(void *addr, size_t length, int prot, int flags,
- int fd, off_t offset);
+ int fd, off64_t offset);
int munmap(void *addr, size_t length);
int ioctl(int fd, unsigned long request, void *arg);
@@ -61,14 +57,13 @@ private:
int start();
int getCameraIndex(int fd);
+ std::shared_ptr<V4L2CameraFile> cameraFile(int fd);
FileOperations fops_;
- CameraManager *cm_;
+ libcamera::CameraManager *cm_;
std::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;
- std::map<int, V4L2CameraProxy *> devices_;
- std::map<void *, V4L2CameraProxy *> mmaps_;
+ std::map<int, std::shared_ptr<V4L2CameraFile>> files_;
+ std::map<void *, std::shared_ptr<V4L2CameraFile>> mmaps_;
};
-
-#endif /* __V4L2_COMPAT_MANAGER_H__ */