libcamera/src/v4l2/v4l2_compat_manager.cpp
Paul Elder 0ce8f2390b v4l2: v4l2_compat: Add V4L2 compatibility layer
Add libcamera V4L2 compatibility layer.

This initial implementation supports the minimal set of V4L2 operations,
which allows getting, setting, and enumerating formats, and streaming
frames from a video device. Some data about the wrapped V4L2 video
device are hardcoded.

Add a build option named 'v4l2' and adjust the build system to
selectively compile the V4L2 compatibility layer.

For now we match the V4L2 device node to a libcamera camera based on a
devnum that a pipeline handler may optionally map to a libcamera
camera.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2020-01-03 19:53:20 -05:00

250 lines
4.9 KiB
C++

/* 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);
}