diff options
Diffstat (limited to 'src/v4l2/v4l2_compat_manager.cpp')
-rw-r--r-- | src/v4l2/v4l2_compat_manager.cpp | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp new file mode 100644 index 00000000..a3d69374 --- /dev/null +++ b/src/v4l2/v4l2_compat_manager.cpp @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * v4l2_compat_manager.cpp - V4L2 compatibility manager + */ + +#include "v4l2_compat_manager.h" + +#include <dlfcn.h> +#include <fcntl.h> +#include <map> +#include <stdarg.h> +#include <string.h> +#include <sys/eventfd.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/camera.h> +#include <libcamera/camera_manager.h> + +#include "log.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(V4L2Compat) + +V4L2CompatManager::V4L2CompatManager() + : cm_(nullptr), initialized_(false) +{ + openat_func_ = (openat_func_t)dlsym(RTLD_NEXT, "openat"); + dup_func_ = (dup_func_t )dlsym(RTLD_NEXT, "dup"); + close_func_ = (close_func_t )dlsym(RTLD_NEXT, "close"); + ioctl_func_ = (ioctl_func_t )dlsym(RTLD_NEXT, "ioctl"); + mmap_func_ = (mmap_func_t )dlsym(RTLD_NEXT, "mmap"); + munmap_func_ = (munmap_func_t)dlsym(RTLD_NEXT, "munmap"); +} + +V4L2CompatManager::~V4L2CompatManager() +{ + devices_.clear(); + mmaps_.clear(); + + if (isRunning()) { + exit(0); + /* \todo Wait with a timeout, just in case. */ + wait(); + } +} + +int V4L2CompatManager::init() +{ + start(); + + MutexLocker locker(mutex_); + cv_.wait(locker, [&] { return initialized_; }); + + return 0; +} + +void V4L2CompatManager::run() +{ + cm_ = new CameraManager(); + + int ret = cm_->start(); + if (ret) { + LOG(V4L2Compat, Error) << "Failed to start camera manager: " + << strerror(-ret); + return; + } + + LOG(V4L2Compat, Debug) << "Started camera manager"; + + /* + * 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()) { + V4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera); + proxies_.emplace_back(proxy); + ++index; + } + + /* + * libcamera has been initialized. Unlock the init() caller as we're + * now ready to handle calls from the framework. + */ + mutex_.lock(); + initialized_ = true; + mutex_.unlock(); + cv_.notify_one(); + + /* Now start processing events and messages. */ + exec(); + + proxies_.clear(); + cm_->stop(); + delete cm_; + cm_ = nullptr; +} + +V4L2CompatManager *V4L2CompatManager::instance() +{ + static V4L2CompatManager instance; + return &instance; +} + +V4L2CameraProxy *V4L2CompatManager::getProxy(int fd) +{ + auto device = devices_.find(fd); + if (device == devices_.end()) + return nullptr; + + return device->second; +} + +int V4L2CompatManager::getCameraIndex(int fd) +{ + struct stat statbuf; + int ret = fstat(fd, &statbuf); + if (ret < 0) + return -1; + + std::shared_ptr<Camera> target = cm_->get(statbuf.st_rdev); + if (!target) + return -1; + + unsigned int index = 0; + for (auto &camera : cm_->cameras()) { + if (camera == target) + return index; + ++index; + } + + return -1; +} + +int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode) +{ + int fd = openat_func_(dirfd, path, oflag, mode); + if (fd < 0) + return fd; + + struct stat statbuf; + int ret = fstat(fd, &statbuf); + if (ret < 0 || (statbuf.st_mode & S_IFMT) != S_IFCHR || + major(statbuf.st_rdev) != 81) + return fd; + + if (!isRunning()) + init(); + + ret = getCameraIndex(fd); + if (ret < 0) { + LOG(V4L2Compat, Info) << "No camera found for " << path; + return fd; + } + + close_func_(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(); + return efd; + } + + devices_.emplace(efd, proxy); + + return efd; +} + +int V4L2CompatManager::dup(int oldfd) +{ + int newfd = dup_func_(oldfd); + if (newfd < 0) + return newfd; + + auto device = devices_.find(oldfd); + if (device != devices_.end()) { + V4L2CameraProxy *proxy = device->second; + devices_[newfd] = proxy; + proxy->dup(); + } + + return newfd; +} + +int V4L2CompatManager::close(int fd) +{ + V4L2CameraProxy *proxy = getProxy(fd); + if (proxy) { + proxy->close(); + devices_.erase(fd); + return 0; + } + + return close_func_(fd); +} + +void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset) +{ + V4L2CameraProxy *proxy = getProxy(fd); + if (!proxy) + return mmap_func_(addr, length, prot, flags, fd, offset); + + void *map = proxy->mmap(length, prot, flags, offset); + if (map == MAP_FAILED) + return map; + + mmaps_[map] = proxy; + return map; +} + +int V4L2CompatManager::munmap(void *addr, size_t length) +{ + auto device = mmaps_.find(addr); + if (device == mmaps_.end()) + return munmap_func_(addr, length); + + V4L2CameraProxy *proxy = device->second; + + int ret = proxy->munmap(addr, length); + if (ret < 0) + return ret; + + mmaps_.erase(device); + + return 0; +} + +int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg) +{ + V4L2CameraProxy *proxy = getProxy(fd); + if (!proxy) + return ioctl_func_(fd, request, arg); + + return proxy->ioctl(request, arg); +} |