diff options
Diffstat (limited to 'src/v4l2/v4l2_camera_proxy.cpp')
-rw-r--r-- | src/v4l2/v4l2_camera_proxy.cpp | 869 |
1 files changed, 629 insertions, 240 deletions
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; } |