diff options
Diffstat (limited to 'src/v4l2')
-rwxr-xr-x | src/v4l2/libcamerify.in | 47 | ||||
-rw-r--r-- | src/v4l2/meson.build | 27 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera.cpp | 157 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera.h | 87 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera_file.cpp | 52 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera_file.h | 40 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera_proxy.cpp | 869 | ||||
-rw-r--r-- | src/v4l2/v4l2_camera_proxy.h | 113 | ||||
-rw-r--r-- | src/v4l2/v4l2_compat.cpp | 93 | ||||
-rw-r--r-- | src/v4l2/v4l2_compat_manager.cpp | 129 | ||||
-rw-r--r-- | src/v4l2/v4l2_compat_manager.h | 23 |
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__ */ |