[{"id":3279,"web_url":"https://patchwork.libcamera.org/comment/3279/","msgid":"<20191227145031.5abrggqog3pqjj5k@uno.localdomain>","date":"2019-12-27T14:50:31","subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Paul,\n   thanks for the progresses!\n\nOn Mon, Dec 23, 2019 at 01:26:19AM -0600, Paul Elder wrote:\n> Add libcamera V4L2 compatibility layer.\n>\n> This initial implementation supports the minimal set of V4L2 operations,\n> which allows getting, setting, and enumerating formats, and streaming\n> frames from a video device. Some data about the wrapped V4L2 video\n> device are hardcoded.\n>\n> Add a build option named 'v4l2' and adjust the build system to\n> selectively compile the V4L2 compatibility layer.\n>\n> For now we match the V4L2 device node to a libcamera camera based on a\n> devnum that a device enumerator may optionally map to a libcamera\n> camera.\n>\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n>\n> ---\n> Major changes in v3:\n> - V4L2CompatManager::openat() verify video node and match to camera via\n>   devnum instead of path\n> - add FrameMetadata class to encapsulate portions of the libcamera\n>   Buffer that needs to be extracted to v4l2_buffer, and to prepare for\n>   the Buffer rework\n> - V4L2CameraProxy refcount for tracking dups and closes (and open)\n> - add V4L2CompatManager::initialized_ to deal with race of waiting on\n>   init cv after the cv is notified\n>\n> Less major changes in v3:\n> - change the list of pending Reqeusts (v4l2 buffers queued before\n>   streamon) to unique pointers\n> - V4L2Camera::updateSizeImage() -> V4L2CameraProxy::calculateSizeImage()\n> - change V4L2CompatManager list of V4L2CameraProxy to unique_ptr, with\n>   maps of fd/mmap addr to V4L2CameraProxy *\n> - removed cross-thread validation methods from V4L2CameraProxy\n>   (validate[Stream|Memory]Type)\n> - removed hasPixelFormat() and hasSize() from V4L2CameraProxy\n> - moved mmap logic out of V4L2Camera and into V4L2CameraProxy\n> - moved nonblock logic out of V4L2Camera and into V4L2CameraProxy\n> - add todos\n> - oter cosmetic changes\n>\n> Changes in v2:\n> - move all errno acrobatics to V4L2CameraProxy\n> - remove all mentions of dmabuf\n> - make V4L2CompatManager::getCamera return pointer rather than\n>   shared_ptr\n> - match V4L2 device nodes to libcamera cameras using Camera::name()\n>   compared to /sys/dev/char/maj:min/name (only works for UVC cameras)\n>   - in V4L2CompatManager::getCameraIndex()\n> - add -fvisibility=hidden to v4l2 compat\n> - cache the results of V4L2CompatManager::imageSize() within V4L2Camera\n>   (where V4L2Camera is interested)\n> - remove V4L2CompatManager::valid[fd|mmap], and where those methods were\n>   used, check V4L2CompatManager::getCamera() != nullptr instead\n> - fixed V4L2CompatManager::drmToV4L2() mappings for DRM_FORMAT_BGR888\n>   and DRM_FORMAT_RGB888\n> - other cosmetic changes\n> ---\n>  meson_options.txt                |   5 +\n>  src/meson.build                  |   4 +\n>  src/v4l2/meson.build             |  31 ++\n>  src/v4l2/v4l2_camera.cpp         | 248 ++++++++++++++++\n>  src/v4l2/v4l2_camera.h           |  84 ++++++\n>  src/v4l2/v4l2_camera_proxy.cpp   | 480 +++++++++++++++++++++++++++++++\n>  src/v4l2/v4l2_camera_proxy.h     |  70 +++++\n>  src/v4l2/v4l2_compat.cpp         |  78 +++++\n>  src/v4l2/v4l2_compat_manager.cpp | 379 ++++++++++++++++++++++++\n>  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n>  10 files changed, 1465 insertions(+)\n>  create mode 100644 src/v4l2/meson.build\n>  create mode 100644 src/v4l2/v4l2_camera.cpp\n>  create mode 100644 src/v4l2/v4l2_camera.h\n>  create mode 100644 src/v4l2/v4l2_camera_proxy.cpp\n>  create mode 100644 src/v4l2/v4l2_camera_proxy.h\n>  create mode 100644 src/v4l2/v4l2_compat.cpp\n>  create mode 100644 src/v4l2/v4l2_compat_manager.cpp\n>  create mode 100644 src/v4l2/v4l2_compat_manager.h\n>\n> diff --git a/meson_options.txt b/meson_options.txt\n> index 1a328045..b06dd494 100644\n> --- a/meson_options.txt\n> +++ b/meson_options.txt\n> @@ -10,3 +10,8 @@ option('documentation',\n>  option('test',\n>          type : 'boolean',\n>          description: 'Compile and include the tests')\n> +\n> +option('v4l2',\n> +        type : 'boolean',\n> +        value : false,\n> +        description : 'Compile libcamera with V4L2 compatibility layer')\n> diff --git a/src/meson.build b/src/meson.build\n> index 67ad20aa..5adcd61f 100644\n> --- a/src/meson.build\n> +++ b/src/meson.build\n> @@ -6,3 +6,7 @@ subdir('libcamera')\n>  subdir('ipa')\n>  subdir('cam')\n>  subdir('qcam')\n> +\n> +if get_option('v4l2')\n> +    subdir('v4l2')\n> +endif\n> diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build\n> new file mode 100644\n> index 00000000..14ee3594\n> --- /dev/null\n> +++ b/src/v4l2/meson.build\n> @@ -0,0 +1,31 @@\n> +v4l2_compat_sources = files([\n> +    'v4l2_camera.cpp',\n> +    'v4l2_camera_proxy.cpp',\n> +    'v4l2_compat.cpp',\n> +    'v4l2_compat_manager.cpp',\n> +])\n> +\n> +v4l2_compat_includes = [\n> +    libcamera_includes,\n> +    libcamera_internal_includes,\n> +]\n> +\n> +v4l2_compat_cpp_args = [\n> +    # Meson enables large file support unconditionally, which redirect file\n> +    # operations to 64-bit versions. This results in some symbols being\n> +    # renamed, for instance open() being renamed to open64(). As the V4L2\n> +    # adaptation wrapper needs to provide both 32-bit and 64-bit versions of\n> +    # file operations, disable transparent large file support.\n> +    '-U_FILE_OFFSET_BITS',\n> +    '-D_FILE_OFFSET_BITS=32',\n> +    '-fvisibility=hidden',\n> +]\n> +\n> +v4l2_compat = shared_library('v4l2-compat',\n> +                             v4l2_compat_sources,\n> +                             name_prefix : '',\n> +                             install : true,\n> +                             link_with : libcamera,\n> +                             include_directories : v4l2_compat_includes,\n> +                             dependencies : libcamera_deps,\n> +                             cpp_args : v4l2_compat_cpp_args)\n> diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> new file mode 100644\n> index 00000000..2d33be9f\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -0,0 +1,248 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_camera.cpp - V4L2 compatibility camera\n> + */\n> +\n> +#include \"v4l2_camera.h\"\n> +\n> +#include <errno.h>\n> +#include <linux/videodev2.h>\n> +#include <sys/mman.h>\n> +#include <sys/syscall.h>\n> +#include <time.h>\n> +#include <unistd.h>\n> +\n> +#include \"log.h\"\n> +#include \"utils.h\"\n> +#include \"v4l2_compat_manager.h\"\n\nI think you can now drop this.\n\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DECLARE_CATEGORY(V4L2Compat);\n> +\n> +FrameMetadata::FrameMetadata(Buffer *buffer)\n> +\t: index_(buffer->index()),\n> +\t  bytesused_(buffer->bytesused()),\n> +\t  timestamp_(buffer->timestamp()),\n> +\t  sequence_(buffer->sequence()),\n> +\t  status_(buffer->status())\n\nplease span 80cols\n\n> +{\n> +}\n> +\n> +V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)\n> +\t: camera_(camera), bufferCount_(0), isRunning_(false)\n> +{\n> +\tcamera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);\n> +};\n\ns/};/}\n\n> +\n> +V4L2Camera::~V4L2Camera()\n> +{\n> +\tcamera_->release();\n\nClosing then destroying a V4L2Camera would cause a double release() ?\n\n> +}\n> +\n> +void V4L2Camera::open(int *ret)\n> +{\n> +\tif (camera_->acquire() < 0) {\n> +\t\tLOG(V4L2Compat, Error) << \"Failed to acquire camera\";\n> +\t\t*ret = -EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\tconfig_ = camera_->generateConfiguration({ StreamRole::Viewfinder });\n> +\tif (!config_) {\n> +\t\tcamera_->release();\n> +\t\t*ret = -EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\t*ret = 0;\n> +}\n> +\n> +void V4L2Camera::close(int *ret)\n> +{\n> +\t*ret = camera_->release();\n> +}\n> +\n> +void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)\n> +{\n> +\t*streamConfig = config_->at(0);\n> +}\n> +\n> +void V4L2Camera::requestComplete(Request *request)\n> +{\n> +\tif (request->status() == Request::RequestCancelled)\n> +\t\treturn;\n> +\n> +\t/* We only have one stream at the moment. */\n> +\tbufferLock_.lock();\n> +\tBuffer *buffer = request->buffers().begin()->second;\n> +\tstd::unique_ptr<FrameMetadata> fmd =\n> +\t\tutils::make_unique<FrameMetadata>(buffer);\n> +\tcompletedBuffers_.push(std::move(fmd));\n> +\tbufferLock_.unlock();\n> +\n> +\tbufferSema_.release();\n> +}\n> +\n> +void V4L2Camera::configure(int *ret, StreamConfiguration *streamConfigOut,\n> +\t\t\t   Size *size, PixelFormat pixelformat,\n> +\t\t\t   unsigned int bufferCount)\n> +{\n> +\tStreamConfiguration &streamConfig = config_->at(0);\n> +\tstreamConfig.size.width = size->width;\n> +\tstreamConfig.size.height = size->height;\n> +\tstreamConfig.pixelFormat = pixelformat;\n> +\tbufferCount_ = bufferCount;\n> +\tstreamConfig.bufferCount = bufferCount_;\n> +\t/* \\todo memoryType (interval vs external) */\n> +\n> +\tCameraConfiguration::Status validation = config_->validate();\n> +\tif (validation == CameraConfiguration::Invalid) {\n> +\t\tLOG(V4L2Compat, Error) << \"Configuration invalid\";\n> +\t\t*ret = -EINVAL;\n> +\t\treturn;\n> +\t}\n> +\tif (validation == CameraConfiguration::Adjusted)\n> +\t\tLOG(V4L2Compat, Info) << \"Configuration adjusted\";\n> +\n> +\tLOG(V4L2Compat, Info) << \"Validated configuration is: \"\n> +\t\t\t      << streamConfig.toString();\n> +\n> +\t*ret = camera_->configure(config_.get());\n> +\tif (*ret < 0)\n> +\t\treturn;\n> +\n> +\tbufferCount_ = streamConfig.bufferCount;\n> +\n> +\t*streamConfigOut = config_->at(0);\n> +}\n> +\n> +void V4L2Camera::mmap(void **ret, unsigned int index)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> +\n> +\tStream *stream = *camera_->streams().begin();\n> +\t*ret = stream->buffers()[index].planes()[0].mem();\n\nShould we check if index is not out of buffers() array bounds ?\n\n> +}\n> +\n> +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> +\n> +\t*ret = camera_->allocateBuffers();\n> +\tif (*ret == -EACCES)\n> +\t\t*ret = -EBUSY;\n> +}\n> +\n> +void V4L2Camera::freeBuffers()\n> +{\n> +\tcamera_->freeBuffers();\n> +\tbufferCount_ = 0;\n\nis bufferCount_ used at all ?\n\n> +}\n> +\n> +void V4L2Camera::streamOn(int *ret)\n> +{\n> +\t*ret = 0;\n> +\n> +\tif (isRunning_)\n> +\t\treturn;\n> +\n> +\t*ret = camera_->start();\n> +\tif (*ret < 0) {\n> +\t\tif (*ret == -EACCES)\n> +\t\t\t*ret = -EBUSY;\n> +\t\treturn;\n> +\t}\n> +\tisRunning_ = true;\n> +\n> +\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> +\t\t/* \\todo What should we do if this returns -EINVAL? */\n> +\t\t*ret = camera_->queueRequest(req.release());\n> +\t\tif (*ret < 0) {\n> +\t\t\t*ret = (*ret == -EACCES ? -EAGAIN : *ret);\n> +\t\t\treturn;\n> +\t\t}\n> +\t}\n> +\n> +\tpendingRequests_.clear();\n> +}\n> +\n> +void V4L2Camera::streamOff(int *ret)\n> +{\n> +\t*ret = 0;\n> +\n> +\t/* \\todo restore buffers to reqbufs state? */\n> +\tif (!isRunning_)\n> +\t\treturn;\n> +\n> +\t*ret = camera_->stop();\n> +\tif (*ret < 0) {\n> +\t\tif (*ret == -EACCES)\n> +\t\t\t*ret = -EBUSY;\n> +\t\treturn;\n> +\t}\n> +\tisRunning_ = false;\n> +}\n> +\n> +void V4L2Camera::qbuf(int *ret, unsigned int index)\n> +{\n> +\tStream *stream = config_->at(0).stream();\n> +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(index);\n> +\tif (!buffer) {\n> +\t\tLOG(V4L2Compat, Error) << \"Can't create buffer\";\n> +\t\t*ret = -ENOMEM;\n> +\t\treturn;\n> +\t}\n> +\n> +\tstd::unique_ptr<Request> request =\n> +\t\tstd::unique_ptr<Request>(camera_->createRequest());\n> +\tif (!request) {\n> +\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> +\t\t*ret = -ENOMEM;\n> +\t\treturn;\n> +\t}\n> +\n> +\t*ret = request->addBuffer(std::move(buffer));\n> +\tif (*ret < 0) {\n> +\t\tLOG(V4L2Compat, Error) << \"Can't set buffer for request\";\n> +\t\t*ret = -ENOMEM;\n> +\t\treturn;\n> +\t}\n> +\n> +\tif (!isRunning_) {\n> +\t\tpendingRequests_.push_back(std::move(request));\n> +\t} else {\n> +\t\t*ret = camera_->queueRequest(request.release());\n> +\t\tif (*ret < 0) {\n> +\t\t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> +\t\t\tif (*ret == -EACCES)\n> +\t\t\t\t*ret = -EBUSY;\n> +\t\t\treturn;\n> +\t\t}\n> +\t}\n> +\n> +\t*ret = 0;\n> +}\n> +\n> +int V4L2Camera::dqbuf(struct v4l2_buffer *arg, bool nonblock)\n> +{\n> +\tif (nonblock && !bufferSema_.tryAcquire())\n> +\t\treturn -EAGAIN;\n> +\telse\n> +\t\tbufferSema_.acquire();\n> +\n> +\tbufferLock_.lock();\n> +\tFrameMetadata *fmd = completedBuffers_.front().get();\n> +\tcompletedBuffers_.pop();\n> +\tbufferLock_.unlock();\n> +\n> +\targ->bytesused = fmd->bytesused();\n> +\targ->field = V4L2_FIELD_NONE;\n> +\targ->timestamp.tv_sec = fmd->timestamp() / 1000000000;\n> +\targ->timestamp.tv_usec = fmd->timestamp() % 1000000;\n> +\targ->sequence = fmd->sequence();\n\nWhat about the buffer index ?\n\n> +\n> +\treturn 0;\n> +}\n> diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> new file mode 100644\n> index 00000000..13418b6b\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -0,0 +1,84 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_camera.h - V4L2 compatibility camera\n> + */\n> +\n> +#ifndef __V4L2_CAMERA_H__\n> +#define __V4L2_CAMERA_H__\n> +\n> +#include <deque>\n> +#include <linux/videodev2.h>\n> +#include <mutex>\n> +#include <queue>\n> +\n> +#include <libcamera/buffer.h>\n> +#include <libcamera/camera.h>\n> +#include \"semaphore.h\"\n> +\n> +using namespace libcamera;\n> +\n> +class FrameMetadata\n> +{\n> +public:\n> +\tFrameMetadata(Buffer *buffer);\n> +\n> +\tint index() const { return index_; }\n> +\n> +\tunsigned int bytesused() const { return bytesused_; }\n> +\tuint64_t timestamp() const { return timestamp_; }\n> +\tunsigned int sequence() const { return sequence_; }\n> +\n> +\tBuffer::Status status() const { return status_; }\n> +\n> +private:\n> +\tint index_;\n> +\n> +\tunsigned int bytesused_;\n> +\tuint64_t timestamp_;\n> +\tunsigned int sequence_;\n> +\n> +\tBuffer::Status status_;\n> +};\n> +\n> +class V4L2Camera : public Object\n> +{\n> +public:\n> +\tV4L2Camera(std::shared_ptr<Camera> camera);\n> +\t~V4L2Camera();\n> +\n> +\tvoid open(int *ret);\n> +\tvoid close(int *ret);\n> +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> +\n> +\tvoid mmap(void **ret, unsigned int index);\n> +\n> +\tvoid configure(int *ret, StreamConfiguration *streamConfigOut, Size *size,\n> +\t\t       PixelFormat pixelformat, unsigned int bufferCount);\n> +\n> +\tvoid allocBuffers(int *ret, unsigned int count);\n> +\tvoid freeBuffers();\n> +\tvoid streamOn(int *ret);\n> +\tvoid streamOff(int *ret);\n> +\n> +\tvoid qbuf(int *ret, unsigned int index);\n> +\tint dqbuf(struct v4l2_buffer *arg, bool nonblock);\n> +\n> +private:\n> +\tvoid requestComplete(Request *request);\n> +\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tstd::unique_ptr<CameraConfiguration> config_;\n> +\n> +\tunsigned int bufferCount_;\n> +\tbool isRunning_;\n> +\n> +\tSemaphore bufferSema_;\n> +\tstd::mutex bufferLock_;\n> +\n> +\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> +\tstd::queue<std::unique_ptr<FrameMetadata>> completedBuffers_;\n> +};\n> +\n> +#endif /* __V4L2_CAMERA_H__ */\n> diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp\n> new file mode 100644\n> index 00000000..b0acd477\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> @@ -0,0 +1,480 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera\n> + */\n> +\n> +#include \"v4l2_camera_proxy.h\"\n> +\n> +#include <algorithm>\n> +#include <linux/videodev2.h>\n> +#include <string.h>\n> +#include <sys/mman.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/object.h>\n> +\n> +#include \"log.h\"\n> +#include \"utils.h\"\n> +#include \"v4l2_camera.h\"\n> +#include \"v4l2_compat_manager.h\"\n> +\n> +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DECLARE_CATEGORY(V4L2Compat);\n> +\n> +V4L2CameraProxy::V4L2CameraProxy(unsigned int index,\n> +\t\t\t\t std::shared_ptr<Camera> camera)\n> +\t: index_(index), bufferCount_(0), currentBuf_(0),\n> +\t  vcam_(utils::make_unique<V4L2Camera>(camera))\n> +{\n> +\tquerycap(camera);\n> +}\n> +\n> +int V4L2CameraProxy::open(bool nonBlocking)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing open\";\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> +\t\t\t    &ret);\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tnonBlocking_ = nonBlocking;\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> +\tsetFmtFromConfig(streamConfig_);\n> +\tsizeimage_ = calculateSizeImage(streamConfig_);\n\nsizeimage_ could be 0, which means the default format is not supported\nby our compat layer. Can this cause troubles later?\n\n> +\n> +\trefcount_++;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void V4L2CameraProxy::dup()\n> +{\n> +\trefcount_++;\n> +}\n> +\n> +int V4L2CameraProxy::close()\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing close\";\n> +\n> +\tif (refcount_ > 1) {\n> +\t\trefcount_--;\n> +\t\treturn 0;\n> +\t}\n\nShould this be locked ?\n\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::close, ConnectionTypeBlocking, &ret);\n> +\tif (ret < 0) {\n> +\t\terrno = EIO;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> +\n> +void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,\n> +\t\t\t    off_t offset)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing mmap\";\n> +\n> +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> +\t\terrno = ENOTSUP;\n> +\t\treturn MAP_FAILED;\n> +\t}\n> +\n> +\tunsigned int index = offset / sizeimage_;\n> +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> +\t\terrno = EINVAL;\n> +\t\treturn MAP_FAILED;\n> +\t}\n> +\n> +\tvoid *val;\n> +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> +\t\t\t    &val, index);\n> +\treturn val;\n> +}\n> +\n> +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing munmap\";\n> +\n> +\tif (length != sizeimage_) {\n> +\t\terrno = EINVAL;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +/* \\todo getDeviceCaps? getMemoryCaps? */\n> +\n> +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> +{\n> +\treturn (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> +}\n> +\n> +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> +{\n> +\treturn (memory == V4L2_MEMORY_MMAP);\n> +}\n> +\n> +void V4L2CameraProxy::setFmtFromConfig(StreamConfiguration &streamConfig)\n> +{\n> +\tcurV4L2Format_.fmt.pix.width = streamConfig.size.width;\n> +\tcurV4L2Format_.fmt.pix.height = streamConfig.size.height;\n> +\tcurV4L2Format_.fmt.pix.pixelformat =\n> +\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat);\n> +\tcurV4L2Format_.fmt.pix.field = V4L2_FIELD_NONE;\n> +\tcurV4L2Format_.fmt.pix.bytesperline =\n> +\t\tV4L2CompatManager::bplMultiplier(\n> +\t\t\tcurV4L2Format_.fmt.pix.pixelformat) *\n> +\t\tcurV4L2Format_.fmt.pix.width;\n> +\tcurV4L2Format_.fmt.pix.sizeimage =\n> +\t\tV4L2CompatManager::imageSize(curV4L2Format_.fmt.pix.pixelformat,\n> +\t\t\t\t\t     curV4L2Format_.fmt.pix.width,\n> +\t\t\t\t\t     curV4L2Format_.fmt.pix.height);\n> +\tcurV4L2Format_.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;\n> +}\n> +\n> +unsigned int V4L2CameraProxy::calculateSizeImage(StreamConfiguration &streamConfig)\n> +{\n> +\treturn V4L2CompatManager::imageSize(\n> +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> +\t\t\tstreamConfig.size.width,\n> +\t\t\tstreamConfig.size.height);\n> +}\n> +\n> +void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)\n> +{\n> +\tstd::string driver = \"libcamera\";\n> +\tstd::string bus_info = driver + \":\" + std::to_string(index_);\n> +\n> +\tmemcpy(capabilities_.driver, driver.c_str(),\n> +\t       sizeof(capabilities_.driver));\n> +\tmemcpy(capabilities_.card, camera->name().c_str(),\n> +\t       sizeof(capabilities_.card));\n> +\tmemcpy(capabilities_.bus_info, bus_info.c_str(),\n> +\t       sizeof(capabilities_.bus_info));\n> +\t/* \\todo Put this is header/config somewhere. */\n> +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> +\tcapabilities_.capabilities =\n> +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\n\nNit:\n\tcapabilities_.capabilities = capabilities_.device_caps |\n\t\t\t\t     V4L2_CAP_DEVICE_CAPS;\n\n> +\tmemset(capabilities_.reserved, 0, sizeof(capabilities_.reserved));\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querycap\";\n> +\n> +\tmemcpy(arg, &capabilities_, sizeof(*arg));\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_enum_fmt\";\n> +\n> +\tif (!validateStreamType(arg->type) ||\n> +\t    arg->index > streamConfig_.formats().pixelformats().size())\n> +\t\treturn -EINVAL;\n> +\n> +\t/* \\todo Add map from format to description. */\n> +\tmemcpy(arg->description, \"asdf\", 5);\n\nCould you at least provide a temporary name a little more meaningful ?\n\n> +\targ->pixelformat = V4L2CompatManager::drmToV4L2(\n> +\t\t\t\tstreamConfig_.formats().pixelformats()[arg->index]);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_g_fmt\";\n> +\n> +\tif (!validateStreamType(arg->type))\n> +\t\treturn -EINVAL;\n> +\n> +\targ->fmt.pix.width        = curV4L2Format_.fmt.pix.width;\n> +\targ->fmt.pix.height       = curV4L2Format_.fmt.pix.height;\n> +\targ->fmt.pix.pixelformat  = curV4L2Format_.fmt.pix.pixelformat;\n> +\targ->fmt.pix.field        = curV4L2Format_.fmt.pix.field;\n> +\targ->fmt.pix.bytesperline = curV4L2Format_.fmt.pix.bytesperline;\n> +\targ->fmt.pix.sizeimage    = curV4L2Format_.fmt.pix.sizeimage;\n> +\targ->fmt.pix.colorspace   = curV4L2Format_.fmt.pix.colorspace;\n\nNit:\nCouldn't you just assign arg->fmt.pix = curV4L2Format_.fmt.pix ?\nIt will make it easier to support multiplanar ?\n\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_s_fmt\";\n> +\n> +\tint ret = vidioc_try_fmt(arg);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> +\t\t\t    &streamConfig_,\n> +\t\t\t    new Size(arg->fmt.pix.width, arg->fmt.pix.height),\n> +\t\t\t    V4L2CompatManager::v4l2ToDrm(arg->fmt.pix.pixelformat),\n> +\t\t\t    bufferCount_);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> +\tif (sizeimage_ == 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tsetFmtFromConfig(streamConfig_);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_try_fmt\";\n> +\tif (!validateStreamType(arg->type))\n> +\t\treturn -EINVAL;\n> +\n> +\tunsigned int format = arg->fmt.pix.pixelformat;\n> +\tconst std::vector<PixelFormat> &formats =\n> +\t\tstreamConfig_.formats().pixelformats();\n> +\tif (std::find(formats.begin(), formats.end(), format) == formats.end())\n> +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> +\n> +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> +\tif (std::find(sizes.begin(), sizes.end(), size) == sizes.end())\n> +\t\tsize = streamConfig_.formats().sizes(format)[0];\n> +\n> +\targ->fmt.pix.width        = size.width;\n> +\targ->fmt.pix.height       = size.height;\n> +\targ->fmt.pix.pixelformat  = format;\n> +\targ->fmt.pix.field        = V4L2_FIELD_NONE;\n> +\targ->fmt.pix.bytesperline =\n> +\t\tV4L2CompatManager::bplMultiplier(\n> +\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> +\t\targ->fmt.pix.width;\n> +\targ->fmt.pix.sizeimage    =\n> +\t\tV4L2CompatManager::imageSize(\n> +\t\t\tV4L2CompatManager::drmToV4L2(format),\n> +\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\nNit:\n\n\targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n\t\t\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n\t\t\t\t\targ->fmt.pix.width;\n\targ->fmt.pix.sizeimage    = V4L2CompatManager::imageSize(\n\t\t\t\t\tV4L2CompatManager::drmToV4L2(format),\n\t\t\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n\n> +\targ->fmt.pix.colorspace   = V4L2_COLORSPACE_SRGB;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)\n> +{\n> +\tint ret;\n> +\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_reqbufs\";\n> +\tif (!validateStreamType(arg->type) ||\n> +\t    !validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\n> +\tLOG(V4L2Compat, Debug) << arg->count << \" bufs requested \";\n> +\n> +\targ->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;\n> +\n> +\tif (arg->count == 0) {\n> +\t\tLOG(V4L2Compat, Debug) << \"Freeing libcamera bufs\";\n> +\t\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> +\t\t\t\t    ConnectionTypeBlocking, &ret);\n> +\t\tif (ret < 0) {\n> +\t\t\tLOG(V4L2Compat, Error) << \"Failed to stop stream\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\t\tvcam_->invokeMethod(&V4L2Camera::freeBuffers,\n> +\t\t\t\t    ConnectionTypeBlocking);\n> +\t\tbufferCount_ = 0;\n> +\t\treturn 0;\n> +\t}\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> +\t\t\t    &streamConfig_,\n> +\t\t\t    new Size(curV4L2Format_.fmt.pix.width,\n> +\t\t\t\t     curV4L2Format_.fmt.pix.height),\n> +\t\t\t    V4L2CompatManager::v4l2ToDrm(curV4L2Format_.fmt.pix.pixelformat),\n> +\t\t\t    arg->count);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> +\tif (sizeimage_ == 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tsetFmtFromConfig(streamConfig_);\n\nDo we need to go through configure() again ?\n\n> +\n> +\targ->count = streamConfig_.bufferCount;\n> +\tbufferCount_ = arg->count;\n> +\n> +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> +\t\treturn -EINVAL;\n\nI would fail earlier in this case.\nAh wait, you do already, don't you?\n\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::allocBuffers,\n> +\t\t\t    ConnectionTypeBlocking, &ret, arg->count);\n> +\tif (ret < 0) {\n> +\t\targ->count = 0;\n> +\t\treturn ret == -EACCES ? -EBUSY : ret;\n> +\t}\n> +\n> +\tLOG(V4L2Compat, Debug) << \"Allocated \" << arg->count << \" buffers\";\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querybuf\";\n> +\tStream *stream = streamConfig_.stream();\n> +\n> +\tif (!validateStreamType(arg->type) ||\n> +\t    arg->index >= stream->buffers().size())\n> +\t\treturn -EINVAL;\n> +\n> +\tunsigned int index = arg->index;\n> +\tmemset(arg, 0, sizeof(*arg));\n> +\targ->index = index;\n> +\targ->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;\n> +\targ->length = curV4L2Format_.fmt.pix.sizeimage;\n> +\targ->memory = V4L2_MEMORY_MMAP;\n> +\targ->m.offset = arg->index * curV4L2Format_.fmt.pix.sizeimage;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_qbuf, index = \"\n> +\t\t\t       << arg->index;\n> +\n> +\tStream *stream = streamConfig_.stream();\n> +\n> +\tif (!validateStreamType(arg->type) ||\n> +\t    !validateMemoryType(arg->memory) ||\n> +\t    arg->index >= stream->buffers().size())\n> +\t\treturn -EINVAL;\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::qbuf, ConnectionTypeBlocking,\n> +\t\t\t    &ret, arg->index);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> +\n> +\treturn ret;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_dqbuf\";\n> +\n> +\tif (!validateStreamType(arg->type) ||\n> +\t    !validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\n> +\targ->index = currentBuf_;\n> +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> +\n> +\tint ret = vcam_->dqbuf(arg, nonBlocking_);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags |= V4L2_BUF_FLAG_DONE;\n> +\n> +\targ->length = sizeimage_;\n> +\n> +\treturn ret;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_streamon(int *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamon\";\n> +\n> +\tif (!validateStreamType(*arg))\n> +\t\treturn -EINVAL;\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::streamOn,\n> +\t\t\t    ConnectionTypeBlocking, &ret);\n> +\treturn ret;\n> +}\n> +\n> +int V4L2CameraProxy::vidioc_streamoff(int *arg)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamoff\";\n> +\n> +\tif (!validateStreamType(*arg))\n> +\t\treturn -EINVAL;\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> +\t\t\t    ConnectionTypeBlocking, &ret);\n> +\treturn ret;\n> +}\n> +\n> +int V4L2CameraProxy::ioctl(unsigned long request, void *arg)\n> +{\n> +\tint ret;\n> +\tswitch (request) {\n> +\tcase VIDIOC_QUERYCAP:\n> +\t\tret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_ENUM_FMT:\n> +\t\tret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_G_FMT:\n> +\t\tret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_S_FMT:\n> +\t\tret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_TRY_FMT:\n> +\t\tret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_REQBUFS:\n> +\t\tret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_QUERYBUF:\n> +\t\tret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_QBUF:\n> +\t\tret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_DQBUF:\n> +\t\tret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_STREAMON:\n> +\t\tret = vidioc_streamon(static_cast<int *>(arg));\n> +\t\tbreak;\n> +\tcase VIDIOC_STREAMOFF:\n> +\t\tret = vidioc_streamoff(static_cast<int *>(arg));\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tret = -ENOTTY;\n> +\t\tbreak;\n> +\t}\n> +\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h\n> new file mode 100644\n> index 00000000..51fdbe19\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.h\n> @@ -0,0 +1,70 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera\n> + */\n> +\n> +#ifndef __V4L2_CAMERA_PROXY_H__\n> +#define __V4L2_CAMERA_PROXY_H__\n> +\n> +#include <linux/videodev2.h>\n> +#include <memory>\n> +#include <sys/types.h>\n> +\n> +#include <libcamera/camera.h>\n> +\n> +#include \"v4l2_camera.h\"\n> +\n> +using namespace libcamera;\n> +\n> +class V4L2CameraProxy\n> +{\n> +public:\n> +\tV4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);\n> +\n> +\tint open(bool nonBlocking);\n> +\tvoid dup();\n> +\tint close();\n> +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> +\t\t   off_t offset);\n> +\tint munmap(void *addr, size_t length);\n> +\n> +\tint ioctl(unsigned long request, void *arg);\n> +\n> +\tunsigned int refcount() { return refcount_; }\n> +\n> +private:\n> +\tbool validateStreamType(uint32_t type);\n> +\tbool validateMemoryType(uint32_t memory);\n> +\tvoid setFmtFromConfig(StreamConfiguration &streamConfig);\n> +\tunsigned int calculateSizeImage(StreamConfiguration &streamConfig);\n> +\tvoid querycap(std::shared_ptr<Camera> camera);\n> +\n> +\tint vidioc_querycap(struct v4l2_capability *arg);\n> +\tint vidioc_enum_fmt(struct v4l2_fmtdesc *arg);\n> +\tint vidioc_g_fmt(struct v4l2_format *arg);\n> +\tint vidioc_s_fmt(struct v4l2_format *arg);\n> +\tint vidioc_try_fmt(struct v4l2_format *arg);\n> +\tint vidioc_reqbufs(struct v4l2_requestbuffers *arg);\n> +\tint vidioc_querybuf(struct v4l2_buffer *arg);\n> +\tint vidioc_qbuf(struct v4l2_buffer *arg);\n> +\tint vidioc_dqbuf(struct v4l2_buffer *arg);\n> +\tint vidioc_streamon(int *arg);\n> +\tint vidioc_streamoff(int *arg);\n> +\n> +\tunsigned int refcount_;\n> +\tunsigned int index_;\n> +\tbool nonBlocking_;\n> +\n> +\tstruct v4l2_format curV4L2Format_;\n> +\tStreamConfiguration streamConfig_;\n> +\tstruct v4l2_capability capabilities_;\n> +\tunsigned int bufferCount_;\n> +\tunsigned int currentBuf_;\n> +\tunsigned int sizeimage_;\n> +\n> +\tstd::unique_ptr<V4L2Camera> vcam_;\n> +};\n> +\n> +#endif /* __V4L2_CAMERA_PROXY_H__ */\n> diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> new file mode 100644\n> index 00000000..a6a7aed2\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat.cpp\n> @@ -0,0 +1,78 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_compat.cpp - V4L2 compatibility layer\n> + */\n> +\n> +#include \"v4l2_compat_manager.h\"\n> +\n> +#include <errno.h>\n> +#include <fcntl.h>\n> +#include <stdarg.h>\n> +#include <sys/mman.h>\n> +#include <sys/stat.h>\n> +#include <sys/types.h>\n> +\n> +#define LIBCAMERA_PUBLIC __attribute__((visibility(\"default\")))\n> +\n> +using namespace libcamera;\n> +\n> +#define extract_va_arg(type, arg, last)\t\\\n> +{\t\t\t\t\t\\\n> +\tva_list ap;\t\t\t\\\n> +\tva_start(ap, last);\t\t\\\n> +\targ = va_arg(ap, type);\t\t\\\n> +\tva_end(ap);\t\t\t\\\n> +}\n> +\n> +extern \"C\" {\n> +\n> +LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)\n> +{\n> +\tmode_t mode = 0;\n> +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> +\t\textract_va_arg(mode_t, mode, oflag);\n> +\n> +\treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag, mode);\n> +}\n> +\n> +LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)\n> +{\n> +\tmode_t mode = 0;\n> +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> +\t\textract_va_arg(mode_t, mode, oflag);\n> +\n> +\treturn V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);\n> +}\n> +\n> +LIBCAMERA_PUBLIC int dup(int oldfd)\n> +{\n> +\treturn V4L2CompatManager::instance()->dup(oldfd);\n> +}\n> +\n> +LIBCAMERA_PUBLIC int close(int fd)\n> +{\n> +\treturn V4L2CompatManager::instance()->close(fd);\n> +}\n> +\n> +LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,\n> +\t\t\t    int fd, off_t offset)\n> +{\n> +\treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> +}\n> +\n> +LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)\n> +{\n> +\treturn V4L2CompatManager::instance()->munmap(addr, length);\n> +}\n> +\n> +LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)\n> +{\n> +\tvoid *arg;\n> +\textract_va_arg(void *, arg, request);\n> +\n> +\treturn V4L2CompatManager::instance()->ioctl(fd, request, arg);\n> +}\n> +\n> +}\n> diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp\n> new file mode 100644\n> index 00000000..bfe76e82\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat_manager.cpp\n> @@ -0,0 +1,379 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_compat_manager.cpp - V4L2 compatibility manager\n> + */\n> +\n> +#include \"v4l2_compat_manager.h\"\n> +\n> +#include <dlfcn.h>\n> +#include <fcntl.h>\n> +#include <fstream>\n> +#include <linux/drm_fourcc.h>\n> +#include <linux/videodev2.h>\n> +#include <map>\n> +#include <stdarg.h>\n> +#include <string.h>\n> +#include <sys/eventfd.h>\n> +#include <sys/mman.h>\n> +#include <sys/stat.h>\n> +#include <sys/sysmacros.h>\n> +#include <sys/types.h>\n> +#include <unistd.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/camera_manager.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"log.h\"\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DEFINE_CATEGORY(V4L2Compat)\n> +\n> +V4L2CompatManager::V4L2CompatManager()\n> +\t: cm_(nullptr), initialized_(false)\n> +{\n> +\topenat_func_ = (openat_func_t)dlsym(RTLD_NEXT, \"openat\");\n> +\tdup_func_    = (dup_func_t   )dlsym(RTLD_NEXT, \"dup\");\n> +\tclose_func_  = (close_func_t )dlsym(RTLD_NEXT, \"close\");\n> +\tioctl_func_  = (ioctl_func_t )dlsym(RTLD_NEXT, \"ioctl\");\n> +\tmmap_func_   = (mmap_func_t  )dlsym(RTLD_NEXT, \"mmap\");\n> +\tmunmap_func_ = (munmap_func_t)dlsym(RTLD_NEXT, \"munmap\");\n> +}\n> +\n> +V4L2CompatManager::~V4L2CompatManager()\n> +{\n> +\tdevices_.clear();\n> +\tmmaps_.clear();\n> +\tproxies_.clear();\n> +\n> +\tif (isRunning()) {\n> +\t\texit(0);\n> +\t\t/* \\todo Wait with a timeout, just in case. */\n> +\t\twait();\n> +\t}\n> +}\n> +\n> +int V4L2CompatManager::init()\n> +{\n> +\tstart();\n> +\n> +\tMutexLocker locker(mutex_);\n> +\tcv_.wait(locker, []{ return V4L2CompatManager::instance()->initialized_; });\n> +\n> +\treturn 0;\n> +}\n> +\n> +void V4L2CompatManager::run()\n> +{\n> +\tcm_ = new CameraManager();\n> +\n> +\tint ret = cm_->start();\n> +\tif (ret) {\n> +\t\tLOG(V4L2Compat, Error) << \"Failed to start camera manager: \"\n> +\t\t\t\t       << strerror(-ret);\n> +\t\treturn;\n> +\t}\n> +\n> +\tLOG(V4L2Compat, Debug) << \"Started camera manager\";\n> +\n> +\t/*\n> +\t * For each Camera registered in the system, a V4L2CameraProxy gets\n> +\t * created here to wrap a camera device.\n> +\t */\n> +\tunsigned int index = 0;\n> +\tfor (auto &camera : cm_->cameras()) {\n> +\t\tV4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);\n> +\t\tproxies_.emplace_back(proxy);\n> +\t\t++index;\n> +\t}\n> +\n> +\t/*\n> +\t * libcamera has been initialized. Unlock the init() caller as we're\n> +\t * now ready to handle calls from the framework.\n> +\t */\n> +\tmutex_.lock();\n> +\tinitialized_ = true;\n> +\tmutex_.unlock();\n> +\tcv_.notify_one();\n> +\n> +\t/* Now start processing events and messages. */\n> +\texec();\n> +\n> +\tcm_->stop();\n> +\tdelete cm_;\n> +\tcm_ = nullptr;\n> +}\n> +\n> +V4L2CompatManager *V4L2CompatManager::instance()\n> +{\n> +\tstatic V4L2CompatManager instance;\n> +\treturn &instance;\n> +}\n> +\n> +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n\nor getProxy() ?\n\n> +{\n> +\tauto device = devices_.find(fd);\n> +\tif (device == devices_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn device->second;\n> +}\n> +\n> +int V4L2CompatManager::getCameraIndex(int fd)\n> +{\n> +\tstruct stat statbuf;\n> +\tfstat(fd, &statbuf);\n> +\tunsigned int devMajor = major(statbuf.st_rdev);\n> +\tunsigned int devMinor = minor(statbuf.st_rdev);\n> +\n> +\tstd::shared_ptr<Camera> target = cm_->get(makedev(devMajor, devMinor));\n\nShouldn't you check for (target != nullptr)\n> +\n> +\tunsigned int index = 0;\n> +\tfor (auto &camera : cm_->cameras()) {\n> +\t\tif (camera == target)\n> +\t\t\tbreak;\n> +\t\t++index;\n> +\t}\n> +\n> +\tif (index >= cm_->cameras().size())\n> +\t\treturn -1;\n\nThis is a bit convoluted...\nA V4l2CameraProxy has the index and Camera, so once you got the target\nCamera from the CameraManager you could use std::find_if<> on proxies_\nwith a V4L2CameraProxy::match(Camera *) function.\n\n> +\n> +\treturn index;\n> +}\n> +\n> +int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode)\n> +{\n> +\tint fd = openat_func_(dirfd, path, oflag, mode);\n> +\tif (fd < 0)\n> +\t\treturn fd;\n> +\n> +\tstruct stat statbuf;\n> +\tfstat(fd, &statbuf);\n> +\tif (major(statbuf.st_rdev) != 81)\n> +\t\treturn fd;\n> +\n> +\tif (!isRunning())\n> +\t\tinit();\n> +\n> +\tint ret = getCameraIndex(fd);\n> +\tif (ret < 0) {\n> +\t\tLOG(V4L2Compat, Info) << \"No camera found for \" << path;\n> +\t\treturn fd;\n\nfd > 0 here\nand fd should be closed before returning (above here too)\n\n> +\t}\n> +\n> +\tclose_func_(fd);\n> +\n> +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> +\n> +\tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> +\tret = proxy->open(oflag & O_NONBLOCK);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tint efd = eventfd(0, (oflag & O_CLOEXEC) | (oflag & O_NONBLOCK));\n> +\tif (efd < 0)\n> +\t\treturn efd;\n> +\n> +\tdevices_.emplace(efd, proxy);\n> +\n> +\treturn efd;\n> +}\n> +\n> +int V4L2CompatManager::dup(int oldfd)\n> +{\n> +\tint newfd = dup_func_(oldfd);\n> +\tif (getCamera(oldfd)) {\n> +\t\tdevices_[oldfd]->dup();\n> +\t\tdevices_[newfd] = devices_[oldfd];\n> +\t}\n> +\n> +\treturn newfd;\n> +}\n> +\n> +int V4L2CompatManager::close(int fd)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(fd);\n> +\tif (proxy) {\n> +\t\tint ret = proxy->close();\n> +\t\tdevices_.erase(fd);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn close_func_(fd);\n> +}\n> +\n> +void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,\n> +\t\t\t      int fd, off_t offset)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(fd);\n> +\tif (!proxy)\n> +\t\treturn mmap_func_(addr, length, prot, flags, fd, offset);\n> +\n> +\tvoid *map = proxy->mmap(addr, length, prot, flags, offset);\n> +\tif (map == MAP_FAILED)\n> +\t\treturn map;\n> +\n> +\tmmaps_[map] = proxy;\n> +\treturn map;\n> +}\n> +\n> +int V4L2CompatManager::munmap(void *addr, size_t length)\n> +{\n> +\tauto device = mmaps_.find(addr);\n> +\tif (device == mmaps_.end())\n> +\t\treturn munmap_func_(addr, length);\n> +\n> +\tV4L2CameraProxy *proxy = device->second;\n> +\n> +\tint ret = proxy->munmap(addr, length);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tmmaps_.erase(device);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(fd);\n> +\tif (!proxy)\n> +\t\treturn ioctl_func_(fd, request, arg);\n> +\n> +\treturn proxy->ioctl(request, arg);\n> +}\n> +\n> +/* \\todo make libcamera export these */\n> +int V4L2CompatManager::bplMultiplier(unsigned int format)\n> +{\n> +\tswitch (format) {\n> +\tcase V4L2_PIX_FMT_NV12:\n> +\tcase V4L2_PIX_FMT_NV21:\n> +\tcase V4L2_PIX_FMT_NV16:\n> +\tcase V4L2_PIX_FMT_NV61:\n> +\tcase V4L2_PIX_FMT_NV24:\n> +\tcase V4L2_PIX_FMT_NV42:\n> +\t\treturn 1;\n> +\tcase V4L2_PIX_FMT_BGR24:\n> +\tcase V4L2_PIX_FMT_RGB24:\n> +\t\treturn 3;\n> +\tcase V4L2_PIX_FMT_ARGB32:\n> +\t\treturn 4;\n> +\tcase V4L2_PIX_FMT_VYUY:\n> +\tcase V4L2_PIX_FMT_YVYU:\n> +\tcase V4L2_PIX_FMT_UYVY:\n> +\tcase V4L2_PIX_FMT_YUYV:\n> +\t\treturn 2;\n> +\tdefault:\n> +\t\treturn 0;\n> +\t};\n> +}\n> +\n> +int V4L2CompatManager::imageSize(unsigned int format,\n> +\t\t\t\t unsigned int width, unsigned int height)\n> +{\n> +\tswitch (format) {\n> +\tcase V4L2_PIX_FMT_NV12:\n> +\tcase V4L2_PIX_FMT_NV21:\n> +\t\treturn width * height + width * height / 2;\n> +\tcase V4L2_PIX_FMT_NV16:\n> +\tcase V4L2_PIX_FMT_NV61:\n> +\t\treturn width * height * 2;\n> +\tcase V4L2_PIX_FMT_NV24:\n> +\tcase V4L2_PIX_FMT_NV42:\n> +\t\treturn width * height * 3;\n> +\tcase V4L2_PIX_FMT_BGR24:\n> +\tcase V4L2_PIX_FMT_RGB24:\n> +\t\treturn width * height * 3;\n> +\tcase V4L2_PIX_FMT_ARGB32:\n> +\t\treturn width * height * 4;\n> +\tcase V4L2_PIX_FMT_VYUY:\n> +\tcase V4L2_PIX_FMT_YVYU:\n> +\tcase V4L2_PIX_FMT_UYVY:\n> +\tcase V4L2_PIX_FMT_YUYV:\n> +\t\treturn width * height * 2;\n> +\tdefault:\n> +\t\treturn 0;\n> +\t};\n> +}\n> +\n> +unsigned int V4L2CompatManager::v4l2ToDrm(unsigned int pixelformat)\n> +{\n> +\tswitch (pixelformat) {\n> +\t/* RGB formats. */\n> +\tcase V4L2_PIX_FMT_RGB24:\n> +\t\treturn DRM_FORMAT_BGR888;\n> +\tcase V4L2_PIX_FMT_BGR24:\n> +\t\treturn DRM_FORMAT_RGB888;\n> +\tcase V4L2_PIX_FMT_ARGB32:\n> +\t\treturn DRM_FORMAT_BGRA8888;\n> +\n> +\t/* YUV packed formats. */\n> +\tcase V4L2_PIX_FMT_YUYV:\n> +\t\treturn DRM_FORMAT_YUYV;\n> +\tcase V4L2_PIX_FMT_YVYU:\n> +\t\treturn DRM_FORMAT_YVYU;\n> +\tcase V4L2_PIX_FMT_UYVY:\n> +\t\treturn DRM_FORMAT_UYVY;\n> +\tcase V4L2_PIX_FMT_VYUY:\n> +\t\treturn DRM_FORMAT_VYUY;\n> +\n> +\t/* YUY planar formats. */\n> +\tcase V4L2_PIX_FMT_NV16:\n> +\t\treturn DRM_FORMAT_NV16;\n> +\tcase V4L2_PIX_FMT_NV61:\n> +\t\treturn DRM_FORMAT_NV61;\n> +\tcase V4L2_PIX_FMT_NV12:\n> +\t\treturn DRM_FORMAT_NV12;\n> +\tcase V4L2_PIX_FMT_NV21:\n> +\t\treturn DRM_FORMAT_NV21;\n> +\tcase V4L2_PIX_FMT_NV24:\n> +\t\treturn DRM_FORMAT_NV24;\n> +\tcase V4L2_PIX_FMT_NV42:\n> +\t\treturn DRM_FORMAT_NV42;\n> +\tdefault:\n> +\t\treturn pixelformat;\n> +\t};\n> +}\n> +\n> +unsigned int V4L2CompatManager::drmToV4L2(unsigned int pixelformat)\n> +{\n> +\tswitch (pixelformat) {\n> +\t/* RGB formats. */\n> +\tcase DRM_FORMAT_BGR888:\n> +\t\treturn V4L2_PIX_FMT_RGB24;\n> +\tcase DRM_FORMAT_RGB888:\n> +\t\treturn V4L2_PIX_FMT_BGR24;\n> +\tcase DRM_FORMAT_BGRA8888:\n> +\t\treturn V4L2_PIX_FMT_ARGB32;\n> +\n> +\t/* YUV packed formats. */\n> +\tcase DRM_FORMAT_YUYV:\n> +\t\treturn V4L2_PIX_FMT_YUYV;\n> +\tcase DRM_FORMAT_YVYU:\n> +\t\treturn V4L2_PIX_FMT_YVYU;\n> +\tcase DRM_FORMAT_UYVY:\n> +\t\treturn V4L2_PIX_FMT_UYVY;\n> +\tcase DRM_FORMAT_VYUY:\n> +\t\treturn V4L2_PIX_FMT_VYUY;\n> +\n> +\t/* YUY planar formats. */\n> +\tcase DRM_FORMAT_NV16:\n> +\t\treturn V4L2_PIX_FMT_NV16;\n> +\tcase DRM_FORMAT_NV61:\n> +\t\treturn V4L2_PIX_FMT_NV61;\n> +\tcase DRM_FORMAT_NV12:\n> +\t\treturn V4L2_PIX_FMT_NV12;\n> +\tcase DRM_FORMAT_NV21:\n> +\t\treturn V4L2_PIX_FMT_NV21;\n> +\tcase DRM_FORMAT_NV24:\n> +\t\treturn V4L2_PIX_FMT_NV24;\n> +\tcase DRM_FORMAT_NV42:\n> +\t\treturn V4L2_PIX_FMT_NV42;\n> +\tdefault:\n> +\t\treturn pixelformat;\n> +\t}\n> +}\n> diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h\n> new file mode 100644\n> index 00000000..b8ae6efe\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat_manager.h\n> @@ -0,0 +1,86 @@\n> +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * v4l2_compat_manager.h - V4L2 compatibility manager\n> + */\n> +\n> +#ifndef __V4L2_COMPAT_MANAGER_H__\n> +#define __V4L2_COMPAT_MANAGER_H__\n> +\n> +#include <condition_variable>\n> +#include <fcntl.h>\n> +#include <map>\n> +#include <mutex>\n> +#include <sys/types.h>\n> +#include <vector>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/camera_manager.h>\n> +#include <libcamera/stream.h>\n\nYou can drop stream.h and camera.h\n\nThanks\n  j\n\n> +\n> +#include \"thread.h\"\n> +#include \"v4l2_camera_proxy.h\"\n> +\n> +using namespace libcamera;\n> +\n> +class V4L2CompatManager : public Thread\n> +{\n> +public:\n> +\tstatic V4L2CompatManager *instance();\n> +\n> +\tint init();\n> +\n> +\tV4L2CameraProxy *getCamera(int fd);\n> +\tV4L2CameraProxy *getCamera(void *addr);\n> +\n> +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> +\n> +\tint dup(int oldfd);\n> +\tint close(int fd);\n> +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> +\t\t   int fd, off_t offset);\n> +\tint munmap(void *addr, size_t length);\n> +\tint ioctl(int fd, unsigned long request, void *arg);\n> +\n> +\tstatic int bplMultiplier(unsigned int format);\n> +\tstatic int imageSize(unsigned int format, unsigned int width,\n> +\t\t\t     unsigned int height);\n> +\n> +\tstatic unsigned int v4l2ToDrm(unsigned int pixelformat);\n> +\tstatic unsigned int drmToV4L2(unsigned int pixelformat);\n> +\n> +private:\n> +\tV4L2CompatManager();\n> +\t~V4L2CompatManager();\n> +\n> +\tvoid run() override;\n> +\tint getCameraIndex(int fd);\n> +\n> +\ttypedef int (*openat_func_t)(int dirfd, const char *path, int oflag, ...);\n> +\ttypedef int (*dup_func_t)(int oldfd);\n> +\ttypedef int (*close_func_t)(int fd);\n> +\ttypedef int (*ioctl_func_t)(int fd, unsigned long request, ...);\n> +\ttypedef void *(*mmap_func_t)(void *addr, size_t length, int prot,\n> +\t\t\t\t     int flags, int fd, off_t offset);\n> +\ttypedef int (*munmap_func_t)(void *addr, size_t length);\n> +\n> +\topenat_func_t openat_func_;\n> +\tdup_func_t    dup_func_;\n> +\tclose_func_t  close_func_;\n> +\tioctl_func_t  ioctl_func_;\n> +\tmmap_func_t   mmap_func_;\n> +\tmunmap_func_t munmap_func_;\n> +\n> +\tCameraManager *cm_;\n> +\n> +\tstd::mutex mutex_;\n> +\tstd::condition_variable cv_;\n> +\tbool initialized_;\n> +\n> +\tstd::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;\n> +\tstd::map<int, V4L2CameraProxy *> devices_;\n> +\tstd::map<void *, V4L2CameraProxy *> mmaps_;\n> +};\n> +\n> +#endif /* __V4L2_COMPAT_MANAGER_H__ */\n> --\n> 2.23.0\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay12.mail.gandi.net (relay12.mail.gandi.net\n\t[217.70.178.232])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id EE6A96046D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 27 Dec 2019 15:48:23 +0100 (CET)","from uno.localdomain (93-34-114-233.ip49.fastwebnet.it\n\t[93.34.114.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay12.mail.gandi.net (Postfix) with ESMTPSA id 0DDA2200002;\n\tFri, 27 Dec 2019 14:48:22 +0000 (UTC)"],"Date":"Fri, 27 Dec 2019 15:50:31 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191227145031.5abrggqog3pqjj5k@uno.localdomain>","References":"<20191223072620.13022-1-paul.elder@ideasonboard.com>\n\t<20191223072620.13022-6-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"wcp4ntkfouzjzy5o\"","Content-Disposition":"inline","In-Reply-To":"<20191223072620.13022-6-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Fri, 27 Dec 2019 14:48:24 -0000"}},{"id":3285,"web_url":"https://patchwork.libcamera.org/comment/3285/","msgid":"<20191228004007.GC5747@pendragon.ideasonboard.com>","date":"2019-12-28T00:40:07","subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Paul,\n\nThank you for the patch.\n\nOn Fri, Dec 27, 2019 at 03:50:31PM +0100, Jacopo Mondi wrote:\n> On Mon, Dec 23, 2019 at 01:26:19AM -0600, Paul Elder wrote:\n> > Add libcamera V4L2 compatibility layer.\n> >\n> > This initial implementation supports the minimal set of V4L2 operations,\n> > which allows getting, setting, and enumerating formats, and streaming\n> > frames from a video device. Some data about the wrapped V4L2 video\n> > device are hardcoded.\n> >\n> > Add a build option named 'v4l2' and adjust the build system to\n> > selectively compile the V4L2 compatibility layer.\n> >\n> > For now we match the V4L2 device node to a libcamera camera based on a\n> > devnum that a device enumerator may optionally map to a libcamera\n\nIsn't it the pipeline handler that maps devnums to cameras, not the\ndevice enumerator ?\n\n> > camera.\n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> >\n> > ---\n> > Major changes in v3:\n> > - V4L2CompatManager::openat() verify video node and match to camera via\n> >   devnum instead of path\n> > - add FrameMetadata class to encapsulate portions of the libcamera\n> >   Buffer that needs to be extracted to v4l2_buffer, and to prepare for\n> >   the Buffer rework\n> > - V4L2CameraProxy refcount for tracking dups and closes (and open)\n> > - add V4L2CompatManager::initialized_ to deal with race of waiting on\n> >   init cv after the cv is notified\n> >\n> > Less major changes in v3:\n> > - change the list of pending Reqeusts (v4l2 buffers queued before\n> >   streamon) to unique pointers\n> > - V4L2Camera::updateSizeImage() -> V4L2CameraProxy::calculateSizeImage()\n> > - change V4L2CompatManager list of V4L2CameraProxy to unique_ptr, with\n> >   maps of fd/mmap addr to V4L2CameraProxy *\n> > - removed cross-thread validation methods from V4L2CameraProxy\n> >   (validate[Stream|Memory]Type)\n> > - removed hasPixelFormat() and hasSize() from V4L2CameraProxy\n> > - moved mmap logic out of V4L2Camera and into V4L2CameraProxy\n> > - moved nonblock logic out of V4L2Camera and into V4L2CameraProxy\n> > - add todos\n> > - oter cosmetic changes\n> >\n> > Changes in v2:\n> > - move all errno acrobatics to V4L2CameraProxy\n> > - remove all mentions of dmabuf\n> > - make V4L2CompatManager::getCamera return pointer rather than\n> >   shared_ptr\n> > - match V4L2 device nodes to libcamera cameras using Camera::name()\n> >   compared to /sys/dev/char/maj:min/name (only works for UVC cameras)\n> >   - in V4L2CompatManager::getCameraIndex()\n> > - add -fvisibility=hidden to v4l2 compat\n> > - cache the results of V4L2CompatManager::imageSize() within V4L2Camera\n> >   (where V4L2Camera is interested)\n> > - remove V4L2CompatManager::valid[fd|mmap], and where those methods were\n> >   used, check V4L2CompatManager::getCamera() != nullptr instead\n> > - fixed V4L2CompatManager::drmToV4L2() mappings for DRM_FORMAT_BGR888\n> >   and DRM_FORMAT_RGB888\n> > - other cosmetic changes\n> > ---\n> >  meson_options.txt                |   5 +\n> >  src/meson.build                  |   4 +\n> >  src/v4l2/meson.build             |  31 ++\n> >  src/v4l2/v4l2_camera.cpp         | 248 ++++++++++++++++\n> >  src/v4l2/v4l2_camera.h           |  84 ++++++\n> >  src/v4l2/v4l2_camera_proxy.cpp   | 480 +++++++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_camera_proxy.h     |  70 +++++\n> >  src/v4l2/v4l2_compat.cpp         |  78 +++++\n> >  src/v4l2/v4l2_compat_manager.cpp | 379 ++++++++++++++++++++++++\n> >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> >  10 files changed, 1465 insertions(+)\n> >  create mode 100644 src/v4l2/meson.build\n> >  create mode 100644 src/v4l2/v4l2_camera.cpp\n> >  create mode 100644 src/v4l2/v4l2_camera.h\n> >  create mode 100644 src/v4l2/v4l2_camera_proxy.cpp\n> >  create mode 100644 src/v4l2/v4l2_camera_proxy.h\n> >  create mode 100644 src/v4l2/v4l2_compat.cpp\n> >  create mode 100644 src/v4l2/v4l2_compat_manager.cpp\n> >  create mode 100644 src/v4l2/v4l2_compat_manager.h\n> >\n> > diff --git a/meson_options.txt b/meson_options.txt\n> > index 1a328045..b06dd494 100644\n> > --- a/meson_options.txt\n> > +++ b/meson_options.txt\n> > @@ -10,3 +10,8 @@ option('documentation',\n> >  option('test',\n> >          type : 'boolean',\n> >          description: 'Compile and include the tests')\n> > +\n> > +option('v4l2',\n> > +        type : 'boolean',\n> > +        value : false,\n> > +        description : 'Compile libcamera with V4L2 compatibility layer')\n\n'Compile the V4L2 compatibility layer'\n\n(I incorrectly told you to disregard my whole comment in v2, when I\nmeant disregarding the s/compatibility/adaptation/)\n\n> > diff --git a/src/meson.build b/src/meson.build\n> > index 67ad20aa..5adcd61f 100644\n> > --- a/src/meson.build\n> > +++ b/src/meson.build\n> > @@ -6,3 +6,7 @@ subdir('libcamera')\n> >  subdir('ipa')\n> >  subdir('cam')\n> >  subdir('qcam')\n> > +\n> > +if get_option('v4l2')\n> > +    subdir('v4l2')\n> > +endif\n> > diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build\n> > new file mode 100644\n> > index 00000000..14ee3594\n> > --- /dev/null\n> > +++ b/src/v4l2/meson.build\n> > @@ -0,0 +1,31 @@\n> > +v4l2_compat_sources = files([\n> > +    'v4l2_camera.cpp',\n> > +    'v4l2_camera_proxy.cpp',\n> > +    'v4l2_compat.cpp',\n> > +    'v4l2_compat_manager.cpp',\n> > +])\n> > +\n> > +v4l2_compat_includes = [\n> > +    libcamera_includes,\n> > +    libcamera_internal_includes,\n> > +]\n> > +\n> > +v4l2_compat_cpp_args = [\n> > +    # Meson enables large file support unconditionally, which redirect file\n> > +    # operations to 64-bit versions. This results in some symbols being\n> > +    # renamed, for instance open() being renamed to open64(). As the V4L2\n> > +    # adaptation wrapper needs to provide both 32-bit and 64-bit versions of\n> > +    # file operations, disable transparent large file support.\n> > +    '-U_FILE_OFFSET_BITS',\n> > +    '-D_FILE_OFFSET_BITS=32',\n> > +    '-fvisibility=hidden',\n> > +]\n> > +\n> > +v4l2_compat = shared_library('v4l2-compat',\n> > +                             v4l2_compat_sources,\n> > +                             name_prefix : '',\n> > +                             install : true,\n> > +                             link_with : libcamera,\n> > +                             include_directories : v4l2_compat_includes,\n> > +                             dependencies : libcamera_deps,\n> > +                             cpp_args : v4l2_compat_cpp_args)\n> > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > new file mode 100644\n> > index 00000000..2d33be9f\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -0,0 +1,248 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_camera.cpp - V4L2 compatibility camera\n> > + */\n> > +\n> > +#include \"v4l2_camera.h\"\n> > +\n> > +#include <errno.h>\n> > +#include <linux/videodev2.h>\n> > +#include <sys/mman.h>\n> > +#include <sys/syscall.h>\n> > +#include <time.h>\n> > +#include <unistd.h>\n> > +\n> > +#include \"log.h\"\n> > +#include \"utils.h\"\n> > +#include \"v4l2_compat_manager.h\"\n> \n> I think you can now drop this.\n> \n> > +\n> > +using namespace libcamera;\n> > +\n> > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > +\n> > +FrameMetadata::FrameMetadata(Buffer *buffer)\n> > +\t: index_(buffer->index()),\n> > +\t  bytesused_(buffer->bytesused()),\n> > +\t  timestamp_(buffer->timestamp()),\n> > +\t  sequence_(buffer->sequence()),\n> > +\t  status_(buffer->status())\n> \n> please span 80cols\n> \n> > +{\n> > +}\n> > +\n> > +V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)\n> > +\t: camera_(camera), bufferCount_(0), isRunning_(false)\n> > +{\n> > +\tcamera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);\n> > +};\n> \n> s/};/}\n> \n> > +\n> > +V4L2Camera::~V4L2Camera()\n> > +{\n> > +\tcamera_->release();\n> \n> Closing then destroying a V4L2Camera would cause a double release() ?\n\nIt can, but I think that's fine, Camera::release() is a no-op in that\ncase.\n\n> > +}\n> > +\n> > +void V4L2Camera::open(int *ret)\n> > +{\n> > +\tif (camera_->acquire() < 0) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Failed to acquire camera\";\n> > +\t\t*ret = -EINVAL;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tconfig_ = camera_->generateConfiguration({ StreamRole::Viewfinder });\n> > +\tif (!config_) {\n> > +\t\tcamera_->release();\n> > +\t\t*ret = -EINVAL;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\t*ret = 0;\n> > +}\n> > +\n> > +void V4L2Camera::close(int *ret)\n> > +{\n> > +\t*ret = camera_->release();\n> > +}\n> > +\n> > +void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)\n> > +{\n> > +\t*streamConfig = config_->at(0);\n> > +}\n> > +\n> > +void V4L2Camera::requestComplete(Request *request)\n> > +{\n> > +\tif (request->status() == Request::RequestCancelled)\n> > +\t\treturn;\n> > +\n> > +\t/* We only have one stream at the moment. */\n> > +\tbufferLock_.lock();\n> > +\tBuffer *buffer = request->buffers().begin()->second;\n> > +\tstd::unique_ptr<FrameMetadata> fmd =\n\ns/fmd/metadata/ ?\n\n> > +\t\tutils::make_unique<FrameMetadata>(buffer);\n> > +\tcompletedBuffers_.push(std::move(fmd));\n> > +\tbufferLock_.unlock();\n> > +\n> > +\tbufferSema_.release();\n> > +}\n> > +\n> > +void V4L2Camera::configure(int *ret, StreamConfiguration *streamConfigOut,\n> > +\t\t\t   Size *size, PixelFormat pixelformat,\n> > +\t\t\t   unsigned int bufferCount)\n> > +{\n> > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > +\tstreamConfig.size.width = size->width;\n> > +\tstreamConfig.size.height = size->height;\n> > +\tstreamConfig.pixelFormat = pixelformat;\n> > +\tbufferCount_ = bufferCount;\n> > +\tstreamConfig.bufferCount = bufferCount_;\n> > +\t/* \\todo memoryType (interval vs external) */\n> > +\n> > +\tCameraConfiguration::Status validation = config_->validate();\n> > +\tif (validation == CameraConfiguration::Invalid) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Configuration invalid\";\n> > +\t\t*ret = -EINVAL;\n> > +\t\treturn;\n> > +\t}\n> > +\tif (validation == CameraConfiguration::Adjusted)\n> > +\t\tLOG(V4L2Compat, Info) << \"Configuration adjusted\";\n> > +\n> > +\tLOG(V4L2Compat, Info) << \"Validated configuration is: \"\n> > +\t\t\t      << streamConfig.toString();\n> > +\n> > +\t*ret = camera_->configure(config_.get());\n> > +\tif (*ret < 0)\n> > +\t\treturn;\n> > +\n> > +\tbufferCount_ = streamConfig.bufferCount;\n> > +\n> > +\t*streamConfigOut = config_->at(0);\n> > +}\n> > +\n> > +void V4L2Camera::mmap(void **ret, unsigned int index)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> > +\n> > +\tStream *stream = *camera_->streams().begin();\n> > +\t*ret = stream->buffers()[index].planes()[0].mem();\n> \n> Should we check if index is not out of buffers() array bounds ?\n\nThis is already checked by the caller.\n\n> > +}\n> > +\n> > +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> > +\n> > +\t*ret = camera_->allocateBuffers();\n> > +\tif (*ret == -EACCES)\n> > +\t\t*ret = -EBUSY;\n> > +}\n> > +\n> > +void V4L2Camera::freeBuffers()\n> > +{\n> > +\tcamera_->freeBuffers();\n> > +\tbufferCount_ = 0;\n> \n> is bufferCount_ used at all ?\n\nNot anymore in this class, it's probably a leftover.\n\n> > +}\n> > +\n> > +void V4L2Camera::streamOn(int *ret)\n> > +{\n> > +\t*ret = 0;\n> > +\n> > +\tif (isRunning_)\n> > +\t\treturn;\n> > +\n> > +\t*ret = camera_->start();\n> > +\tif (*ret < 0) {\n> > +\t\tif (*ret == -EACCES)\n> > +\t\t\t*ret = -EBUSY;\n> > +\t\treturn;\n> > +\t}\n> > +\tisRunning_ = true;\n> > +\n> > +\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > +\t\t/* \\todo What should we do if this returns -EINVAL? */\n\nGood point, we'll have to implement better error handling here. Thanks\nfor capturing it.\n\n> > +\t\t*ret = camera_->queueRequest(req.release());\n> > +\t\tif (*ret < 0) {\n> > +\t\t\t*ret = (*ret == -EACCES ? -EAGAIN : *ret);\n> > +\t\t\treturn;\n> > +\t\t}\n> > +\t}\n> > +\n> > +\tpendingRequests_.clear();\n> > +}\n> > +\n> > +void V4L2Camera::streamOff(int *ret)\n> > +{\n> > +\t*ret = 0;\n> > +\n> > +\t/* \\todo restore buffers to reqbufs state? */\n> > +\tif (!isRunning_)\n> > +\t\treturn;\n> > +\n> > +\t*ret = camera_->stop();\n> > +\tif (*ret < 0) {\n> > +\t\tif (*ret == -EACCES)\n> > +\t\t\t*ret = -EBUSY;\n> > +\t\treturn;\n> > +\t}\n> > +\tisRunning_ = false;\n> > +}\n> > +\n> > +void V4L2Camera::qbuf(int *ret, unsigned int index)\n> > +{\n> > +\tStream *stream = config_->at(0).stream();\n> > +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(index);\n> > +\tif (!buffer) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Can't create buffer\";\n> > +\t\t*ret = -ENOMEM;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tstd::unique_ptr<Request> request =\n> > +\t\tstd::unique_ptr<Request>(camera_->createRequest());\n> > +\tif (!request) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > +\t\t*ret = -ENOMEM;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\t*ret = request->addBuffer(std::move(buffer));\n> > +\tif (*ret < 0) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Can't set buffer for request\";\n> > +\t\t*ret = -ENOMEM;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tif (!isRunning_) {\n> > +\t\tpendingRequests_.push_back(std::move(request));\n> > +\t} else {\n> > +\t\t*ret = camera_->queueRequest(request.release());\n> > +\t\tif (*ret < 0) {\n> > +\t\t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> > +\t\t\tif (*ret == -EACCES)\n> > +\t\t\t\t*ret = -EBUSY;\n> > +\t\t\treturn;\n> > +\t\t}\n> > +\t}\n> > +\n> > +\t*ret = 0;\n> > +}\n> > +\n> > +int V4L2Camera::dqbuf(struct v4l2_buffer *arg, bool nonblock)\n> > +{\n> > +\tif (nonblock && !bufferSema_.tryAcquire())\n> > +\t\treturn -EAGAIN;\n> > +\telse\n> > +\t\tbufferSema_.acquire();\n> > +\n> > +\tbufferLock_.lock();\n> > +\tFrameMetadata *fmd = completedBuffers_.front().get();\n> > +\tcompletedBuffers_.pop();\n> > +\tbufferLock_.unlock();\n> > +\n> > +\targ->bytesused = fmd->bytesused();\n> > +\targ->field = V4L2_FIELD_NONE;\n> > +\targ->timestamp.tv_sec = fmd->timestamp() / 1000000000;\n> > +\targ->timestamp.tv_usec = fmd->timestamp() % 1000000;\n> > +\targ->sequence = fmd->sequence();\n> \n> What about the buffer index ?\n\nIt's set by the caller.\n\nI'll review the qbuf/dqbuf strategy on patch 6/6.\n\n> > +\n> > +\treturn 0;\n> > +}\n> > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > new file mode 100644\n> > index 00000000..13418b6b\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -0,0 +1,84 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_camera.h - V4L2 compatibility camera\n> > + */\n> > +\n> > +#ifndef __V4L2_CAMERA_H__\n> > +#define __V4L2_CAMERA_H__\n> > +\n> > +#include <deque>\n> > +#include <linux/videodev2.h>\n> > +#include <mutex>\n> > +#include <queue>\n> > +\n> > +#include <libcamera/buffer.h>\n> > +#include <libcamera/camera.h>\n> > +#include \"semaphore.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +class FrameMetadata\n> > +{\n> > +public:\n> > +\tFrameMetadata(Buffer *buffer);\n> > +\n> > +\tint index() const { return index_; }\n> > +\n> > +\tunsigned int bytesused() const { return bytesused_; }\n> > +\tuint64_t timestamp() const { return timestamp_; }\n> > +\tunsigned int sequence() const { return sequence_; }\n> > +\n> > +\tBuffer::Status status() const { return status_; }\n> > +\n> > +private:\n> > +\tint index_;\n> > +\n> > +\tunsigned int bytesused_;\n> > +\tuint64_t timestamp_;\n> > +\tunsigned int sequence_;\n> > +\n> > +\tBuffer::Status status_;\n> > +};\n> > +\n> > +class V4L2Camera : public Object\n> > +{\n> > +public:\n> > +\tV4L2Camera(std::shared_ptr<Camera> camera);\n> > +\t~V4L2Camera();\n> > +\n> > +\tvoid open(int *ret);\n> > +\tvoid close(int *ret);\n> > +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> > +\n> > +\tvoid mmap(void **ret, unsigned int index);\n> > +\n> > +\tvoid configure(int *ret, StreamConfiguration *streamConfigOut, Size *size,\n> > +\t\t       PixelFormat pixelformat, unsigned int bufferCount);\n> > +\n> > +\tvoid allocBuffers(int *ret, unsigned int count);\n> > +\tvoid freeBuffers();\n> > +\tvoid streamOn(int *ret);\n> > +\tvoid streamOff(int *ret);\n> > +\n> > +\tvoid qbuf(int *ret, unsigned int index);\n> > +\tint dqbuf(struct v4l2_buffer *arg, bool nonblock);\n> > +\n> > +private:\n> > +\tvoid requestComplete(Request *request);\n> > +\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tstd::unique_ptr<CameraConfiguration> config_;\n> > +\n> > +\tunsigned int bufferCount_;\n> > +\tbool isRunning_;\n> > +\n> > +\tSemaphore bufferSema_;\n> > +\tstd::mutex bufferLock_;\n> > +\n> > +\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > +\tstd::queue<std::unique_ptr<FrameMetadata>> completedBuffers_;\n> > +};\n> > +\n> > +#endif /* __V4L2_CAMERA_H__ */\n> > diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp\n> > new file mode 100644\n> > index 00000000..b0acd477\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> > @@ -0,0 +1,480 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera\n> > + */\n> > +\n> > +#include \"v4l2_camera_proxy.h\"\n> > +\n> > +#include <algorithm>\n> > +#include <linux/videodev2.h>\n> > +#include <string.h>\n> > +#include <sys/mman.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/object.h>\n> > +\n> > +#include \"log.h\"\n> > +#include \"utils.h\"\n> > +#include \"v4l2_camera.h\"\n> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))\n> > +\n> > +using namespace libcamera;\n> > +\n> > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > +\n> > +V4L2CameraProxy::V4L2CameraProxy(unsigned int index,\n> > +\t\t\t\t std::shared_ptr<Camera> camera)\n> > +\t: index_(index), bufferCount_(0), currentBuf_(0),\n> > +\t  vcam_(utils::make_unique<V4L2Camera>(camera))\n\nYou need to initialize refcount_ to 0.\n\n> > +{\n> > +\tquerycap(camera);\n> > +}\n> > +\n> > +int V4L2CameraProxy::open(bool nonBlocking)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing open\";\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> > +\t\t\t    &ret);\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\tnonBlocking_ = nonBlocking;\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > +\tsetFmtFromConfig(streamConfig_);\n> > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> \n> sizeimage_ could be 0, which means the default format is not supported\n> by our compat layer. Can this cause troubles later?\n\nPotentially, but that should not happen as the compat layer should\nsupport all formats supported by libcamera. I wonder if we could\nsomehow enforce this at compilation time. Moving the DRM <-> V4L2\nformat conversion functions to the libcamera core could help in this\narea. For now I think we can ignore the issue.\n\n> > +\n> > +\trefcount_++;\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +void V4L2CameraProxy::dup()\n> > +{\n> > +\trefcount_++;\n> > +}\n> > +\n> > +int V4L2CameraProxy::close()\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing close\";\n> > +\n> > +\tif (refcount_ > 1) {\n> > +\t\trefcount_--;\n\nYou need to decrement refcount_ even if == 1. I would thus write\n\n\tif (--refcount_ > 0)\n\t\treturn 0;\n\n> > +\t\treturn 0;\n> > +\t}\n> \n> Should this be locked ?\n\nEventually, but it's not the only thing that needs to be locked. It just\noccurred to me that we are very thread-unsafe if the application using\nthe compat layer is threaded. That's OK, one step at a time :-) Let's\njust remember about it.\n\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::close, ConnectionTypeBlocking, &ret);\n> > +\tif (ret < 0) {\n> > +\t\terrno = EIO;\n> > +\t\treturn -1;\n> > +\t}\n\nThis ends up calling Camera::release() and forwarding the error up, and\nthe only possible cause of error is an invalid state of the camera,\nwhich would result from a bug in the V4L2 adaptation layer. Let's\nsimplify the code (saving us from handling refcount_ specially in case\nof close() errors) and avoid dealing with close() errors by making this\nmethod and V4L2Camera::close() void.\n\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,\n> > +\t\t\t    off_t offset)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing mmap\";\n> > +\n> > +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> > +\t\terrno = ENOTSUP;\n> > +\t\treturn MAP_FAILED;\n> > +\t}\n> > +\n> > +\tunsigned int index = offset / sizeimage_;\n> > +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> > +\t\terrno = EINVAL;\n> > +\t\treturn MAP_FAILED;\n> > +\t}\n> > +\n> > +\tvoid *val;\n> > +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> > +\t\t\t    &val, index);\n> > +\treturn val;\n> > +}\n> > +\n> > +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing munmap\";\n> > +\n> > +\tif (length != sizeimage_) {\n> > +\t\terrno = EINVAL;\n> > +\t\treturn -1;\n> > +\t}\n\nWe should probably also keep a list of mapped addresses and check that\naddr is in that list. This can be left for later, but please add a \\todo\nitem in that case.\n\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +/* \\todo getDeviceCaps? getMemoryCaps? */\n\nWhat is this ?\n\n> > +\n> > +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> > +{\n> > +\treturn (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n\nNo need for parentheses.\n\n> > +}\n> > +\n> > +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> > +{\n> > +\treturn (memory == V4L2_MEMORY_MMAP);\n\nSame here.\n\n> > +}\n> > +\n> > +void V4L2CameraProxy::setFmtFromConfig(StreamConfiguration &streamConfig)\n> > +{\n> > +\tcurV4L2Format_.fmt.pix.width = streamConfig.size.width;\n> > +\tcurV4L2Format_.fmt.pix.height = streamConfig.size.height;\n> > +\tcurV4L2Format_.fmt.pix.pixelformat =\n> > +\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat);\n> > +\tcurV4L2Format_.fmt.pix.field = V4L2_FIELD_NONE;\n> > +\tcurV4L2Format_.fmt.pix.bytesperline =\n> > +\t\tV4L2CompatManager::bplMultiplier(\n> > +\t\t\tcurV4L2Format_.fmt.pix.pixelformat) *\n> > +\t\tcurV4L2Format_.fmt.pix.width;\n> > +\tcurV4L2Format_.fmt.pix.sizeimage =\n> > +\t\tV4L2CompatManager::imageSize(curV4L2Format_.fmt.pix.pixelformat,\n> > +\t\t\t\t\t     curV4L2Format_.fmt.pix.width,\n> > +\t\t\t\t\t     curV4L2Format_.fmt.pix.height);\n> > +\tcurV4L2Format_.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;\n> > +}\n> > +\n> > +unsigned int V4L2CameraProxy::calculateSizeImage(StreamConfiguration &streamConfig)\n> > +{\n> > +\treturn V4L2CompatManager::imageSize(\n> > +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> > +\t\t\tstreamConfig.size.width,\n> > +\t\t\tstreamConfig.size.height);\n> > +}\n> > +\n> > +void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)\n\nThis is an instance method, no need to pass camera as an argument, you\ncan use vcam_.\n\n> > +{\n> > +\tstd::string driver = \"libcamera\";\n> > +\tstd::string bus_info = driver + \":\" + std::to_string(index_);\n> > +\n> > +\tmemcpy(capabilities_.driver, driver.c_str(),\n> > +\t       sizeof(capabilities_.driver));\n> > +\tmemcpy(capabilities_.card, camera->name().c_str(),\n> > +\t       sizeof(capabilities_.card));\n> > +\tmemcpy(capabilities_.bus_info, bus_info.c_str(),\n> > +\t       sizeof(capabilities_.bus_info));\n> > +\t/* \\todo Put this is header/config somewhere. */\n\ns/is/in/\n\n> > +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> > +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> > +\tcapabilities_.capabilities =\n> > +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\n> \n> Nit:\n> \tcapabilities_.capabilities = capabilities_.device_caps |\n> \t\t\t\t     V4L2_CAP_DEVICE_CAPS;\n\n \tcapabilities_.capabilities = capabilities_.device_caps\n \t\t\t\t   | V4L2_CAP_DEVICE_CAPS;\n\n:-)\n\n> > +\tmemset(capabilities_.reserved, 0, sizeof(capabilities_.reserved));\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querycap\";\n> > +\n> > +\tmemcpy(arg, &capabilities_, sizeof(*arg));\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_enum_fmt\";\n> > +\n> > +\tif (!validateStreamType(arg->type) ||\n> > +\t    arg->index > streamConfig_.formats().pixelformats().size())\n> > +\t\treturn -EINVAL;\n> > +\n> > +\t/* \\todo Add map from format to description. */\n> > +\tmemcpy(arg->description, \"asdf\", 5);\n> \n> Could you at least provide a temporary name a little more meaningful ?\n\nAgreed.\n\n> > +\targ->pixelformat = V4L2CompatManager::drmToV4L2(\n> > +\t\t\t\tstreamConfig_.formats().pixelformats()[arg->index]);\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_g_fmt\";\n> > +\n> > +\tif (!validateStreamType(arg->type))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\targ->fmt.pix.width        = curV4L2Format_.fmt.pix.width;\n> > +\targ->fmt.pix.height       = curV4L2Format_.fmt.pix.height;\n> > +\targ->fmt.pix.pixelformat  = curV4L2Format_.fmt.pix.pixelformat;\n> > +\targ->fmt.pix.field        = curV4L2Format_.fmt.pix.field;\n> > +\targ->fmt.pix.bytesperline = curV4L2Format_.fmt.pix.bytesperline;\n> > +\targ->fmt.pix.sizeimage    = curV4L2Format_.fmt.pix.sizeimage;\n> > +\targ->fmt.pix.colorspace   = curV4L2Format_.fmt.pix.colorspace;\n> \n> Nit:\n> Couldn't you just assign arg->fmt.pix = curV4L2Format_.fmt.pix ?\n> It will make it easier to support multiplanar ?\n\nv4l2_pix_format doesn't contain pointers, so this should be sage and\nwill simplify the code. Note that we should likely\n\n\tmemset(&arg->fmt, 0, sizeof(arg->fmt));\n\nbefore the assignment.\n\nIt will be interesting to run v4l2-compliance on this :-)\n\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_s_fmt\";\n> > +\n> > +\tint ret = vidioc_try_fmt(arg);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > +\t\t\t    &streamConfig_,\n> > +\t\t\t    new Size(arg->fmt.pix.width, arg->fmt.pix.height),\n\nYou're leaking the newly allocate Size. Why don't you pass a const Size\n& instead of a Size * ? Were you trying to work around a compilation\nerror ? :-)\n\n> > +\t\t\t    V4L2CompatManager::v4l2ToDrm(arg->fmt.pix.pixelformat),\n> > +\t\t\t    bufferCount_);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > +\tif (sizeimage_ == 0)\n> > +\t\treturn -EINVAL;\n\nA VIDIOC_S_FMT error shouldn't change the active configuration of the\ndevice. You thus need to work on a local copy of sizeimage_.\n\n> > +\n> > +\tsetFmtFromConfig(streamConfig_);\n\nYou always call calculateSizeImage() and setFmtFromConfig() together,\nand calculateSizeImage() actually duplicates some code from\nsetFmtFromConfig(). I would thus remove calculateSizeImage() and turn\nsetFmtFromConfig() into an int function that will not perform any change\nto the active config if an error has to be returned. You could then\nremove sizeimage_ and replace it with curV4L2Format_.fmt.pix.sizeimage\n(with appropriate local variables if needed to keep the code readable).\n\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_try_fmt\";\n> > +\tif (!validateStreamType(arg->type))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tunsigned int format = arg->fmt.pix.pixelformat;\n> > +\tconst std::vector<PixelFormat> &formats =\n> > +\t\tstreamConfig_.formats().pixelformats();\n> > +\tif (std::find(formats.begin(), formats.end(), format) == formats.end())\n> > +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> > +\n> > +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> > +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> > +\tif (std::find(sizes.begin(), sizes.end(), size) == sizes.end())\n> > +\t\tsize = streamConfig_.formats().sizes(format)[0];\n> > +\n> > +\targ->fmt.pix.width        = size.width;\n> > +\targ->fmt.pix.height       = size.height;\n> > +\targ->fmt.pix.pixelformat  = format;\n> > +\targ->fmt.pix.field        = V4L2_FIELD_NONE;\n> > +\targ->fmt.pix.bytesperline =\n> > +\t\tV4L2CompatManager::bplMultiplier(\n> > +\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> > +\t\targ->fmt.pix.width;\n> > +\targ->fmt.pix.sizeimage    =\n> > +\t\tV4L2CompatManager::imageSize(\n> > +\t\t\tV4L2CompatManager::drmToV4L2(format),\n> > +\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n>\n> Nit:\n> \n> \targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n> \t\t\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> \t\t\t\t\targ->fmt.pix.width;\n> \targ->fmt.pix.sizeimage    = V4L2CompatManager::imageSize(\n> \t\t\t\t\tV4L2CompatManager::drmToV4L2(format),\n> \t\t\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n\nI'm not sure it's much easier to read. The good news is that moving\nbplMultiplier() and imageSize() to the V4L2CameraProxy class (as\nexplained below) will help.\n\n> > +\targ->fmt.pix.colorspace   = V4L2_COLORSPACE_SRGB;\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)\n> > +{\n> > +\tint ret;\n> > +\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_reqbufs\";\n> > +\tif (!validateStreamType(arg->type) ||\n> > +\t    !validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tLOG(V4L2Compat, Debug) << arg->count << \" bufs requested \";\n> > +\n> > +\targ->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;\n> > +\n> > +\tif (arg->count == 0) {\n> > +\t\tLOG(V4L2Compat, Debug) << \"Freeing libcamera bufs\";\n> > +\t\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > +\t\t\t\t    ConnectionTypeBlocking, &ret);\n> > +\t\tif (ret < 0) {\n> > +\t\t\tLOG(V4L2Compat, Error) << \"Failed to stop stream\";\n> > +\t\t\treturn ret;\n> > +\t\t}\n> > +\t\tvcam_->invokeMethod(&V4L2Camera::freeBuffers,\n> > +\t\t\t\t    ConnectionTypeBlocking);\n> > +\t\tbufferCount_ = 0;\n> > +\t\treturn 0;\n> > +\t}\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > +\t\t\t    &streamConfig_,\n> > +\t\t\t    new Size(curV4L2Format_.fmt.pix.width,\n> > +\t\t\t\t     curV4L2Format_.fmt.pix.height),\n> > +\t\t\t    V4L2CompatManager::v4l2ToDrm(curV4L2Format_.fmt.pix.pixelformat),\n> > +\t\t\t    arg->count);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > +\tif (sizeimage_ == 0)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tsetFmtFromConfig(streamConfig_);\n> \n> Do we need to go through configure() again ?\n\nI was going to ask the same :-) It is valid for a V4L2 application to\nopen a device and call VIDIOC_REQBUFS, so we need to handle that.\nCalling configure() here is an option, but maybe setting an initial\nconfiguration at open() time would be better.\n\n> > +\n> > +\targ->count = streamConfig_.bufferCount;\n> > +\tbufferCount_ = arg->count;\n> > +\n> > +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> > +\t\treturn -EINVAL;\n> \n> I would fail earlier in this case.\n> Ah wait, you do already, don't you?\n\nGood catch :-)\n\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::allocBuffers,\n> > +\t\t\t    ConnectionTypeBlocking, &ret, arg->count);\n> > +\tif (ret < 0) {\n> > +\t\targ->count = 0;\n> > +\t\treturn ret == -EACCES ? -EBUSY : ret;\n> > +\t}\n> > +\n> > +\tLOG(V4L2Compat, Debug) << \"Allocated \" << arg->count << \" buffers\";\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querybuf\";\n> > +\tStream *stream = streamConfig_.stream();\n> > +\n> > +\tif (!validateStreamType(arg->type) ||\n> > +\t    arg->index >= stream->buffers().size())\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tunsigned int index = arg->index;\n> > +\tmemset(arg, 0, sizeof(*arg));\n> > +\targ->index = index;\n> > +\targ->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;\n> > +\targ->length = curV4L2Format_.fmt.pix.sizeimage;\n> > +\targ->memory = V4L2_MEMORY_MMAP;\n> > +\targ->m.offset = arg->index * curV4L2Format_.fmt.pix.sizeimage;\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_qbuf, index = \"\n> > +\t\t\t       << arg->index;\n> > +\n> > +\tStream *stream = streamConfig_.stream();\n> > +\n> > +\tif (!validateStreamType(arg->type) ||\n> > +\t    !validateMemoryType(arg->memory) ||\n> > +\t    arg->index >= stream->buffers().size())\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::qbuf, ConnectionTypeBlocking,\n> > +\t\t\t    &ret, arg->index);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n\nThe V4L2_BUF_FLAG_MAPPED flag should be set at mmap() time and cleared\nan munmap() time. It shouldn't be touched here.\n\n> > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_dqbuf\";\n> > +\n> > +\tif (!validateStreamType(arg->type) ||\n> > +\t    !validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\targ->index = currentBuf_;\n> > +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> > +\n> > +\tint ret = vcam_->dqbuf(arg, nonBlocking_);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags |= V4L2_BUF_FLAG_DONE;\n> > +\n> > +\targ->length = sizeimage_;\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_streamon(int *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamon\";\n> > +\n> > +\tif (!validateStreamType(*arg))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::streamOn,\n> > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > +\treturn ret;\n> > +}\n> > +\n> > +int V4L2CameraProxy::vidioc_streamoff(int *arg)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamoff\";\n> > +\n> > +\tif (!validateStreamType(*arg))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > +\treturn ret;\n> > +}\n> > +\n> > +int V4L2CameraProxy::ioctl(unsigned long request, void *arg)\n> > +{\n> > +\tint ret;\n> > +\tswitch (request) {\n> > +\tcase VIDIOC_QUERYCAP:\n> > +\t\tret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_ENUM_FMT:\n> > +\t\tret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_G_FMT:\n> > +\t\tret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_S_FMT:\n> > +\t\tret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_TRY_FMT:\n> > +\t\tret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_REQBUFS:\n> > +\t\tret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_QUERYBUF:\n> > +\t\tret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_QBUF:\n> > +\t\tret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_DQBUF:\n> > +\t\tret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_STREAMON:\n> > +\t\tret = vidioc_streamon(static_cast<int *>(arg));\n> > +\t\tbreak;\n> > +\tcase VIDIOC_STREAMOFF:\n> > +\t\tret = vidioc_streamoff(static_cast<int *>(arg));\n> > +\t\tbreak;\n> > +\tdefault:\n> > +\t\tret = -ENOTTY;\n> > +\t\tbreak;\n> > +\t}\n> > +\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\treturn ret;\n> > +}\n> > diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h\n> > new file mode 100644\n> > index 00000000..51fdbe19\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.h\n> > @@ -0,0 +1,70 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera\n> > + */\n> > +\n> > +#ifndef __V4L2_CAMERA_PROXY_H__\n> > +#define __V4L2_CAMERA_PROXY_H__\n> > +\n> > +#include <linux/videodev2.h>\n> > +#include <memory>\n> > +#include <sys/types.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +\n> > +#include \"v4l2_camera.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +class V4L2CameraProxy\n> > +{\n> > +public:\n> > +\tV4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);\n> > +\n> > +\tint open(bool nonBlocking);\n> > +\tvoid dup();\n> > +\tint close();\n> > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > +\t\t   off_t offset);\n> > +\tint munmap(void *addr, size_t length);\n> > +\n> > +\tint ioctl(unsigned long request, void *arg);\n> > +\n> > +\tunsigned int refcount() { return refcount_; }\n\nThis method is not used, you can drop it.\n\n> > +\n> > +private:\n> > +\tbool validateStreamType(uint32_t type);\n> > +\tbool validateMemoryType(uint32_t memory);\n> > +\tvoid setFmtFromConfig(StreamConfiguration &streamConfig);\n> > +\tunsigned int calculateSizeImage(StreamConfiguration &streamConfig);\n> > +\tvoid querycap(std::shared_ptr<Camera> camera);\n> > +\n> > +\tint vidioc_querycap(struct v4l2_capability *arg);\n> > +\tint vidioc_enum_fmt(struct v4l2_fmtdesc *arg);\n> > +\tint vidioc_g_fmt(struct v4l2_format *arg);\n> > +\tint vidioc_s_fmt(struct v4l2_format *arg);\n> > +\tint vidioc_try_fmt(struct v4l2_format *arg);\n> > +\tint vidioc_reqbufs(struct v4l2_requestbuffers *arg);\n> > +\tint vidioc_querybuf(struct v4l2_buffer *arg);\n> > +\tint vidioc_qbuf(struct v4l2_buffer *arg);\n> > +\tint vidioc_dqbuf(struct v4l2_buffer *arg);\n> > +\tint vidioc_streamon(int *arg);\n> > +\tint vidioc_streamoff(int *arg);\n> > +\n> > +\tunsigned int refcount_;\n> > +\tunsigned int index_;\n> > +\tbool nonBlocking_;\n> > +\n> > +\tstruct v4l2_format curV4L2Format_;\n> > +\tStreamConfiguration streamConfig_;\n> > +\tstruct v4l2_capability capabilities_;\n> > +\tunsigned int bufferCount_;\n> > +\tunsigned int currentBuf_;\n> > +\tunsigned int sizeimage_;\n> > +\n> > +\tstd::unique_ptr<V4L2Camera> vcam_;\n> > +};\n> > +\n> > +#endif /* __V4L2_CAMERA_PROXY_H__ */\n> > diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> > new file mode 100644\n> > index 00000000..a6a7aed2\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat.cpp\n> > @@ -0,0 +1,78 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_compat.cpp - V4L2 compatibility layer\n> > + */\n> > +\n> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#include <errno.h>\n> > +#include <fcntl.h>\n> > +#include <stdarg.h>\n> > +#include <sys/mman.h>\n> > +#include <sys/stat.h>\n> > +#include <sys/types.h>\n> > +\n> > +#define LIBCAMERA_PUBLIC __attribute__((visibility(\"default\")))\n> > +\n> > +using namespace libcamera;\n> > +\n> > +#define extract_va_arg(type, arg, last)\t\\\n> > +{\t\t\t\t\t\\\n> > +\tva_list ap;\t\t\t\\\n> > +\tva_start(ap, last);\t\t\\\n> > +\targ = va_arg(ap, type);\t\t\\\n> > +\tva_end(ap);\t\t\t\\\n> > +}\n> > +\n> > +extern \"C\" {\n> > +\n> > +LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)\n> > +{\n> > +\tmode_t mode = 0;\n> > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > +\t\textract_va_arg(mode_t, mode, oflag);\n> > +\n> > +\treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag, mode);\n\nCould you wrap lines at a 80 columns boundary when feasible ?\n\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)\n> > +{\n> > +\tmode_t mode = 0;\n> > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > +\t\textract_va_arg(mode_t, mode, oflag);\n> > +\n> > +\treturn V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC int dup(int oldfd)\n> > +{\n> > +\treturn V4L2CompatManager::instance()->dup(oldfd);\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC int close(int fd)\n> > +{\n> > +\treturn V4L2CompatManager::instance()->close(fd);\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,\n> > +\t\t\t    int fd, off_t offset)\n> > +{\n> > +\treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n\nSame comment here.\n\nIt's annoying that clang-format doesn't implement a soft boundary and a\nhard boundary.\n\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)\n> > +{\n> > +\treturn V4L2CompatManager::instance()->munmap(addr, length);\n> > +}\n> > +\n> > +LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)\n> > +{\n> > +\tvoid *arg;\n> > +\textract_va_arg(void *, arg, request);\n> > +\n> > +\treturn V4L2CompatManager::instance()->ioctl(fd, request, arg);\n> > +}\n> > +\n> > +}\n> > diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp\n> > new file mode 100644\n> > index 00000000..bfe76e82\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > @@ -0,0 +1,379 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_compat_manager.cpp - V4L2 compatibility manager\n> > + */\n> > +\n> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#include <dlfcn.h>\n> > +#include <fcntl.h>\n> > +#include <fstream>\n> > +#include <linux/drm_fourcc.h>\n> > +#include <linux/videodev2.h>\n> > +#include <map>\n> > +#include <stdarg.h>\n> > +#include <string.h>\n> > +#include <sys/eventfd.h>\n> > +#include <sys/mman.h>\n> > +#include <sys/stat.h>\n> > +#include <sys/sysmacros.h>\n> > +#include <sys/types.h>\n> > +#include <unistd.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/camera_manager.h>\n> > +#include <libcamera/stream.h>\n> > +\n> > +#include \"log.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +LOG_DEFINE_CATEGORY(V4L2Compat)\n> > +\n> > +V4L2CompatManager::V4L2CompatManager()\n> > +\t: cm_(nullptr), initialized_(false)\n> > +{\n> > +\topenat_func_ = (openat_func_t)dlsym(RTLD_NEXT, \"openat\");\n> > +\tdup_func_    = (dup_func_t   )dlsym(RTLD_NEXT, \"dup\");\n> > +\tclose_func_  = (close_func_t )dlsym(RTLD_NEXT, \"close\");\n> > +\tioctl_func_  = (ioctl_func_t )dlsym(RTLD_NEXT, \"ioctl\");\n> > +\tmmap_func_   = (mmap_func_t  )dlsym(RTLD_NEXT, \"mmap\");\n> > +\tmunmap_func_ = (munmap_func_t)dlsym(RTLD_NEXT, \"munmap\");\n> > +}\n> > +\n> > +V4L2CompatManager::~V4L2CompatManager()\n> > +{\n> > +\tdevices_.clear();\n> > +\tmmaps_.clear();\n> > +\tproxies_.clear();\n\nWouldn't it be better to clear proxies_ after stopping the thread ? The\nvector stores unique pointers, clearing it will delete all proxies,\nwhich in turn deletes the V4L2Camera instances that are accessible from\nthe thread.\n\n> > +\n> > +\tif (isRunning()) {\n> > +\t\texit(0);\n> > +\t\t/* \\todo Wait with a timeout, just in case. */\n> > +\t\twait();\n> > +\t}\n> > +}\n> > +\n> > +int V4L2CompatManager::init()\n> > +{\n> > +\tstart();\n> > +\n> > +\tMutexLocker locker(mutex_);\n> > +\tcv_.wait(locker, []{ return V4L2CompatManager::instance()->initialized_; });\n\nWe're in an instance method, so there's no need to go through\nV4L2CompatManager::instance(), you can return initialized_; directly.\nThe compiler will then throw an error:\n\n../../src/v4l2/v4l2_compat_manager.cpp: In lambda function:\n../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: ‘this’ was not captured for this lambda function\n  cv_.wait(locker, []{ return initialized_; });\n                              ^~~~~~~~~~~~\n../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: invalid use of non-static data member ‘V4L2CompatManager::initialized_’\nIn file included from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n../../src/v4l2/v4l2_compat_manager.h:79:7: note: declared here\n  bool initialized_;\n       ^~~~~~~~~~~~\nIn file included from ../../src/v4l2/v4l2_compat_manager.h:11,\n                 from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n/usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable: In instantiation of ‘void std::condition_variable::wait(std::unique_lock<std::mutex>&, _Predicate) [with _Predicate = V4L2CompatManager::init()::<lambda()>]’:\n../../src/v4l2/v4l2_compat_manager.cpp:64:45:   required from here\n/usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:13: error: could not convert ‘__p.V4L2CompatManager::init()::<lambda()>()’ from ‘void’ to ‘bool’\n  while (!__p())\n          ~~~^~\n/usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:9: error: in argument to unary !\n  while (!__p())\n         ^~~~~~\n\nwhich I assume led you to using V4L2CompatManager::instance(). The right\nfix is to capture 'this' for the lambda function. You can read mode\nabout lambda functions and captures at\nhttps://en.cppreference.com/w/cpp/language/lambda.\n\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +void V4L2CompatManager::run()\n> > +{\n> > +\tcm_ = new CameraManager();\n> > +\n> > +\tint ret = cm_->start();\n> > +\tif (ret) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Failed to start camera manager: \"\n> > +\t\t\t\t       << strerror(-ret);\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tLOG(V4L2Compat, Debug) << \"Started camera manager\";\n> > +\n> > +\t/*\n> > +\t * For each Camera registered in the system, a V4L2CameraProxy gets\n> > +\t * created here to wrap a camera device.\n> > +\t */\n> > +\tunsigned int index = 0;\n> > +\tfor (auto &camera : cm_->cameras()) {\n> > +\t\tV4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);\n> > +\t\tproxies_.emplace_back(proxy);\n> > +\t\t++index;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * libcamera has been initialized. Unlock the init() caller as we're\n> > +\t * now ready to handle calls from the framework.\n> > +\t */\n> > +\tmutex_.lock();\n> > +\tinitialized_ = true;\n> > +\tmutex_.unlock();\n> > +\tcv_.notify_one();\n> > +\n> > +\t/* Now start processing events and messages. */\n> > +\texec();\n> > +\n> > +\tcm_->stop();\n> > +\tdelete cm_;\n> > +\tcm_ = nullptr;\n> > +}\n> > +\n> > +V4L2CompatManager *V4L2CompatManager::instance()\n> > +{\n> > +\tstatic V4L2CompatManager instance;\n> > +\treturn &instance;\n> > +}\n> > +\n> > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> \n> or getProxy() ?\n\nGood idea.\n\n> > +{\n> > +\tauto device = devices_.find(fd);\n> > +\tif (device == devices_.end())\n> > +\t\treturn nullptr;\n> > +\n> > +\treturn device->second;\n> > +}\n> > +\n> > +int V4L2CompatManager::getCameraIndex(int fd)\n> > +{\n> > +\tstruct stat statbuf;\n> > +\tfstat(fd, &statbuf);\n> > +\tunsigned int devMajor = major(statbuf.st_rdev);\n> > +\tunsigned int devMinor = minor(statbuf.st_rdev);\n> > +\n> > +\tstd::shared_ptr<Camera> target = cm_->get(makedev(devMajor, devMinor));\n> \n> Shouldn't you check for (target != nullptr)\n\nAgreed.\n\n> > +\n> > +\tunsigned int index = 0;\n> > +\tfor (auto &camera : cm_->cameras()) {\n> > +\t\tif (camera == target)\n> > +\t\t\tbreak;\n> > +\t\t++index;\n> > +\t}\n> > +\n> > +\tif (index >= cm_->cameras().size())\n> > +\t\treturn -1;\n> \n> This is a bit convoluted...\n> A V4l2CameraProxy has the index and Camera, so once you got the target\n> Camera from the CameraManager you could use std::find_if<> on proxies_\n> with a V4L2CameraProxy::match(Camera *) function.\n\nIs it worth the additional complexity though, given that both are O(n)\nanyway (and with a small n) ? I think we'll have to revisit this to\nhandle hotplug in any case, so I'd keep it as-is.\n\n> > +\n> > +\treturn index;\n> > +}\n> > +\n> > +int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode)\n> > +{\n> > +\tint fd = openat_func_(dirfd, path, oflag, mode);\n> > +\tif (fd < 0)\n> > +\t\treturn fd;\n> > +\n> > +\tstruct stat statbuf;\n> > +\tfstat(fd, &statbuf);\n> > +\tif (major(statbuf.st_rdev) != 81)\n\nIn the very unlikely case that statbuf.st_rdev is initialized to 81 due\nto random stack data, an fstat() failure won't be caught here. I woult\nthus test\n\n\tif (ret < 0 || major(statbuf.st_rdev) != 81)\n\nFor completeness you should also test that the device is a character\ndevice and not a block device, so\n\n\tif (ret < 0 || (statbuf.st_mode & S_IFMT) != S_IFCHR ||\n\t    major(statbuf.st_rdev) != 81)\n\n> > +\t\treturn fd;\n> > +\n> > +\tif (!isRunning())\n> > +\t\tinit();\n> > +\n> > +\tint ret = getCameraIndex(fd);\n> > +\tif (ret < 0) {\n> > +\t\tLOG(V4L2Compat, Info) << \"No camera found for \" << path;\n> > +\t\treturn fd;\n> \n> fd > 0 here\n> and fd should be closed before returning (above here too)\n\nI think this is actually correct, the device isn't handled by libcamera,\nso we forward openat().\n\n> > +\t}\n> > +\n> > +\tclose_func_(fd);\n> > +\n> > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > +\n> > +\tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> > +\tret = proxy->open(oflag & O_NONBLOCK);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tint efd = eventfd(0, (oflag & O_CLOEXEC) | (oflag & O_NONBLOCK));\n\nDo you prefer this over\n\n\tint efd = eventfd(0, oflag & (O_CLOEXEC | O_NONBLOCK));\n\nfor a specific reason ? There's no need to change it if you prefer your\nversion, I'm just curious.\n\n> > +\tif (efd < 0)\n> > +\t\treturn efd;\n> > +\n> > +\tdevices_.emplace(efd, proxy);\n> > +\n> > +\treturn efd;\n> > +}\n> > +\n> > +int V4L2CompatManager::dup(int oldfd)\n> > +{\n> > +\tint newfd = dup_func_(oldfd);\n> > +\tif (getCamera(oldfd)) {\n> > +\t\tdevices_[oldfd]->dup();\n> > +\t\tdevices_[newfd] = devices_[oldfd];\n> > +\t}\n\nYou would avoid a few lookups with\n\n\tauto device = devices_.find(oldfd);\n\tif (device != devices_.end()) {\n\t\tV4L2CameraProxy *proxy = device->second;\n\t\tdevices_[newfd] = proxy;\n\t\tproxy->dup();\n\t}\n\n> > +\n> > +\treturn newfd;\n> > +}\n> > +\n> > +int V4L2CompatManager::close(int fd)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > +\tif (proxy) {\n> > +\t\tint ret = proxy->close();\n> > +\t\tdevices_.erase(fd);\n> > +\t\treturn ret;\n> > +\t}\n> > +\n> > +\treturn close_func_(fd);\n> > +}\n> > +\n> > +void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,\n> > +\t\t\t      int fd, off_t offset)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > +\tif (!proxy)\n> > +\t\treturn mmap_func_(addr, length, prot, flags, fd, offset);\n> > +\n> > +\tvoid *map = proxy->mmap(addr, length, prot, flags, offset);\n> > +\tif (map == MAP_FAILED)\n> > +\t\treturn map;\n> > +\n> > +\tmmaps_[map] = proxy;\n> > +\treturn map;\n> > +}\n> > +\n> > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > +{\n> > +\tauto device = mmaps_.find(addr);\n> > +\tif (device == mmaps_.end())\n> > +\t\treturn munmap_func_(addr, length);\n> > +\n> > +\tV4L2CameraProxy *proxy = device->second;\n> > +\n> > +\tint ret = proxy->munmap(addr, length);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tmmaps_.erase(device);\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > +\tif (!proxy)\n> > +\t\treturn ioctl_func_(fd, request, arg);\n> > +\n> > +\treturn proxy->ioctl(request, arg);\n> > +}\n> > +\n> > +/* \\todo make libcamera export these */\n> > +int V4L2CompatManager::bplMultiplier(unsigned int format)\n> > +{\n> > +\tswitch (format) {\n> > +\tcase V4L2_PIX_FMT_NV12:\n> > +\tcase V4L2_PIX_FMT_NV21:\n> > +\tcase V4L2_PIX_FMT_NV16:\n> > +\tcase V4L2_PIX_FMT_NV61:\n> > +\tcase V4L2_PIX_FMT_NV24:\n> > +\tcase V4L2_PIX_FMT_NV42:\n> > +\t\treturn 1;\n> > +\tcase V4L2_PIX_FMT_BGR24:\n> > +\tcase V4L2_PIX_FMT_RGB24:\n> > +\t\treturn 3;\n> > +\tcase V4L2_PIX_FMT_ARGB32:\n> > +\t\treturn 4;\n> > +\tcase V4L2_PIX_FMT_VYUY:\n> > +\tcase V4L2_PIX_FMT_YVYU:\n> > +\tcase V4L2_PIX_FMT_UYVY:\n> > +\tcase V4L2_PIX_FMT_YUYV:\n> > +\t\treturn 2;\n> > +\tdefault:\n> > +\t\treturn 0;\n> > +\t};\n> > +}\n> > +\n> > +int V4L2CompatManager::imageSize(unsigned int format,\n> > +\t\t\t\t unsigned int width, unsigned int height)\n> > +{\n> > +\tswitch (format) {\n> > +\tcase V4L2_PIX_FMT_NV12:\n> > +\tcase V4L2_PIX_FMT_NV21:\n> > +\t\treturn width * height + width * height / 2;\n> > +\tcase V4L2_PIX_FMT_NV16:\n> > +\tcase V4L2_PIX_FMT_NV61:\n> > +\t\treturn width * height * 2;\n> > +\tcase V4L2_PIX_FMT_NV24:\n> > +\tcase V4L2_PIX_FMT_NV42:\n> > +\t\treturn width * height * 3;\n> > +\tcase V4L2_PIX_FMT_BGR24:\n> > +\tcase V4L2_PIX_FMT_RGB24:\n> > +\t\treturn width * height * 3;\n> > +\tcase V4L2_PIX_FMT_ARGB32:\n> > +\t\treturn width * height * 4;\n> > +\tcase V4L2_PIX_FMT_VYUY:\n> > +\tcase V4L2_PIX_FMT_YVYU:\n> > +\tcase V4L2_PIX_FMT_UYVY:\n> > +\tcase V4L2_PIX_FMT_YUYV:\n> > +\t\treturn width * height * 2;\n> > +\tdefault:\n> > +\t\treturn 0;\n> > +\t};\n> > +}\n> > +\n> > +unsigned int V4L2CompatManager::v4l2ToDrm(unsigned int pixelformat)\n> > +{\n> > +\tswitch (pixelformat) {\n> > +\t/* RGB formats. */\n> > +\tcase V4L2_PIX_FMT_RGB24:\n> > +\t\treturn DRM_FORMAT_BGR888;\n> > +\tcase V4L2_PIX_FMT_BGR24:\n> > +\t\treturn DRM_FORMAT_RGB888;\n> > +\tcase V4L2_PIX_FMT_ARGB32:\n> > +\t\treturn DRM_FORMAT_BGRA8888;\n> > +\n> > +\t/* YUV packed formats. */\n> > +\tcase V4L2_PIX_FMT_YUYV:\n> > +\t\treturn DRM_FORMAT_YUYV;\n> > +\tcase V4L2_PIX_FMT_YVYU:\n> > +\t\treturn DRM_FORMAT_YVYU;\n> > +\tcase V4L2_PIX_FMT_UYVY:\n> > +\t\treturn DRM_FORMAT_UYVY;\n> > +\tcase V4L2_PIX_FMT_VYUY:\n> > +\t\treturn DRM_FORMAT_VYUY;\n> > +\n> > +\t/* YUY planar formats. */\n> > +\tcase V4L2_PIX_FMT_NV16:\n> > +\t\treturn DRM_FORMAT_NV16;\n> > +\tcase V4L2_PIX_FMT_NV61:\n> > +\t\treturn DRM_FORMAT_NV61;\n> > +\tcase V4L2_PIX_FMT_NV12:\n> > +\t\treturn DRM_FORMAT_NV12;\n> > +\tcase V4L2_PIX_FMT_NV21:\n> > +\t\treturn DRM_FORMAT_NV21;\n> > +\tcase V4L2_PIX_FMT_NV24:\n> > +\t\treturn DRM_FORMAT_NV24;\n> > +\tcase V4L2_PIX_FMT_NV42:\n> > +\t\treturn DRM_FORMAT_NV42;\n> > +\tdefault:\n> > +\t\treturn pixelformat;\n> > +\t};\n> > +}\n> > +\n> > +unsigned int V4L2CompatManager::drmToV4L2(unsigned int pixelformat)\n> > +{\n> > +\tswitch (pixelformat) {\n> > +\t/* RGB formats. */\n> > +\tcase DRM_FORMAT_BGR888:\n> > +\t\treturn V4L2_PIX_FMT_RGB24;\n> > +\tcase DRM_FORMAT_RGB888:\n> > +\t\treturn V4L2_PIX_FMT_BGR24;\n> > +\tcase DRM_FORMAT_BGRA8888:\n> > +\t\treturn V4L2_PIX_FMT_ARGB32;\n> > +\n> > +\t/* YUV packed formats. */\n> > +\tcase DRM_FORMAT_YUYV:\n> > +\t\treturn V4L2_PIX_FMT_YUYV;\n> > +\tcase DRM_FORMAT_YVYU:\n> > +\t\treturn V4L2_PIX_FMT_YVYU;\n> > +\tcase DRM_FORMAT_UYVY:\n> > +\t\treturn V4L2_PIX_FMT_UYVY;\n> > +\tcase DRM_FORMAT_VYUY:\n> > +\t\treturn V4L2_PIX_FMT_VYUY;\n> > +\n> > +\t/* YUY planar formats. */\n> > +\tcase DRM_FORMAT_NV16:\n> > +\t\treturn V4L2_PIX_FMT_NV16;\n> > +\tcase DRM_FORMAT_NV61:\n> > +\t\treturn V4L2_PIX_FMT_NV61;\n> > +\tcase DRM_FORMAT_NV12:\n> > +\t\treturn V4L2_PIX_FMT_NV12;\n> > +\tcase DRM_FORMAT_NV21:\n> > +\t\treturn V4L2_PIX_FMT_NV21;\n> > +\tcase DRM_FORMAT_NV24:\n> > +\t\treturn V4L2_PIX_FMT_NV24;\n> > +\tcase DRM_FORMAT_NV42:\n> > +\t\treturn V4L2_PIX_FMT_NV42;\n> > +\tdefault:\n> > +\t\treturn pixelformat;\n> > +\t}\n> > +}\n> > diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h\n> > new file mode 100644\n> > index 00000000..b8ae6efe\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat_manager.h\n> > @@ -0,0 +1,86 @@\n> > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * v4l2_compat_manager.h - V4L2 compatibility manager\n> > + */\n> > +\n> > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > +#define __V4L2_COMPAT_MANAGER_H__\n> > +\n> > +#include <condition_variable>\n> > +#include <fcntl.h>\n> > +#include <map>\n> > +#include <mutex>\n> > +#include <sys/types.h>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/camera_manager.h>\n> > +#include <libcamera/stream.h>\n> \n> You can drop stream.h and camera.h\n> \n> > +\n> > +#include \"thread.h\"\n> > +#include \"v4l2_camera_proxy.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +class V4L2CompatManager : public Thread\n> > +{\n> > +public:\n> > +\tstatic V4L2CompatManager *instance();\n> > +\n> > +\tint init();\n> > +\n> > +\tV4L2CameraProxy *getCamera(int fd);\n> > +\tV4L2CameraProxy *getCamera(void *addr);\n> > +\n> > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > +\n> > +\tint dup(int oldfd);\n> > +\tint close(int fd);\n> > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > +\t\t   int fd, off_t offset);\n> > +\tint munmap(void *addr, size_t length);\n> > +\tint ioctl(int fd, unsigned long request, void *arg);\n> > +\n> > +\tstatic int bplMultiplier(unsigned int format);\n> > +\tstatic int imageSize(unsigned int format, unsigned int width,\n> > +\t\t\t     unsigned int height);\n> > +\n> > +\tstatic unsigned int v4l2ToDrm(unsigned int pixelformat);\n> > +\tstatic unsigned int drmToV4L2(unsigned int pixelformat);\n\nThose four methods are now used by the V4L2CameraProxy class only, you\ncan move them there and make them private.\n\n> > +\n> > +private:\n> > +\tV4L2CompatManager();\n> > +\t~V4L2CompatManager();\n> > +\n> > +\tvoid run() override;\n> > +\tint getCameraIndex(int fd);\n> > +\n> > +\ttypedef int (*openat_func_t)(int dirfd, const char *path, int oflag, ...);\n> > +\ttypedef int (*dup_func_t)(int oldfd);\n> > +\ttypedef int (*close_func_t)(int fd);\n> > +\ttypedef int (*ioctl_func_t)(int fd, unsigned long request, ...);\n> > +\ttypedef void *(*mmap_func_t)(void *addr, size_t length, int prot,\n> > +\t\t\t\t     int flags, int fd, off_t offset);\n> > +\ttypedef int (*munmap_func_t)(void *addr, size_t length);\n> > +\n> > +\topenat_func_t openat_func_;\n> > +\tdup_func_t    dup_func_;\n> > +\tclose_func_t  close_func_;\n> > +\tioctl_func_t  ioctl_func_;\n> > +\tmmap_func_t   mmap_func_;\n> > +\tmunmap_func_t munmap_func_;\n> > +\n> > +\tCameraManager *cm_;\n> > +\n> > +\tstd::mutex mutex_;\n> > +\tstd::condition_variable cv_;\n> > +\tbool initialized_;\n> > +\n> > +\tstd::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;\n> > +\tstd::map<int, V4L2CameraProxy *> devices_;\n> > +\tstd::map<void *, V4L2CameraProxy *> mmaps_;\n> > +};\n> > +\n> > +#endif /* __V4L2_COMPAT_MANAGER_H__ */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0312E6046A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 28 Dec 2019 01:40:19 +0100 (CET)","from pendragon.ideasonboard.com\n\t(host-149-154-202-187.dynamic.voo.be [149.154.202.187])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3C115DD;\n\tSat, 28 Dec 2019 01:40:19 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1577493619;\n\tbh=7Qvt+LuUWJvTWA/mvrxAC4KqWC4TT1P1pkcIr3jUpMU=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=ksM5TZV12LQ6dH7F7vhDxkO0pGI4yT9ECp6SJd+BDvH7xYv5oqXDHOvw+ikIeGC/Q\n\tlyi3eUJUwCBN8okVINKJtADRZ6/9+hoE0qvdcjdmTNG9vYE4azqecssvPRjwrX65J6\n\tiHaAXqkhswLaK/Pe0Fpr2IHUHqQAOR5TNm/cm8cM=","Date":"Sat, 28 Dec 2019 02:40:07 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20191228004007.GC5747@pendragon.ideasonboard.com>","References":"<20191223072620.13022-1-paul.elder@ideasonboard.com>\n\t<20191223072620.13022-6-paul.elder@ideasonboard.com>\n\t<20191227145031.5abrggqog3pqjj5k@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20191227145031.5abrggqog3pqjj5k@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Sat, 28 Dec 2019 00:40:20 -0000"}},{"id":3288,"web_url":"https://patchwork.libcamera.org/comment/3288/","msgid":"<20191231054041.GA24428@localhost.localdomain>","date":"2019-12-31T05:40:41","subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Laurent,\n\nThank you for the review.\n\nOn Sat, Dec 28, 2019 at 02:40:07AM +0200, Laurent Pinchart wrote:\n> Hi Paul,\n> \n> Thank you for the patch.\n> \n> On Fri, Dec 27, 2019 at 03:50:31PM +0100, Jacopo Mondi wrote:\n> > On Mon, Dec 23, 2019 at 01:26:19AM -0600, Paul Elder wrote:\n> > > Add libcamera V4L2 compatibility layer.\n> > >\n> > > This initial implementation supports the minimal set of V4L2 operations,\n> > > which allows getting, setting, and enumerating formats, and streaming\n> > > frames from a video device. Some data about the wrapped V4L2 video\n> > > device are hardcoded.\n> > >\n> > > Add a build option named 'v4l2' and adjust the build system to\n> > > selectively compile the V4L2 compatibility layer.\n> > >\n> > > For now we match the V4L2 device node to a libcamera camera based on a\n> > > devnum that a device enumerator may optionally map to a libcamera\n> \n> Isn't it the pipeline handler that maps devnums to cameras, not the\n> device enumerator ?\n> \n> > > camera.\n> > >\n> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > >\n> > > ---\n> > > Major changes in v3:\n> > > - V4L2CompatManager::openat() verify video node and match to camera via\n> > >   devnum instead of path\n> > > - add FrameMetadata class to encapsulate portions of the libcamera\n> > >   Buffer that needs to be extracted to v4l2_buffer, and to prepare for\n> > >   the Buffer rework\n> > > - V4L2CameraProxy refcount for tracking dups and closes (and open)\n> > > - add V4L2CompatManager::initialized_ to deal with race of waiting on\n> > >   init cv after the cv is notified\n> > >\n> > > Less major changes in v3:\n> > > - change the list of pending Reqeusts (v4l2 buffers queued before\n> > >   streamon) to unique pointers\n> > > - V4L2Camera::updateSizeImage() -> V4L2CameraProxy::calculateSizeImage()\n> > > - change V4L2CompatManager list of V4L2CameraProxy to unique_ptr, with\n> > >   maps of fd/mmap addr to V4L2CameraProxy *\n> > > - removed cross-thread validation methods from V4L2CameraProxy\n> > >   (validate[Stream|Memory]Type)\n> > > - removed hasPixelFormat() and hasSize() from V4L2CameraProxy\n> > > - moved mmap logic out of V4L2Camera and into V4L2CameraProxy\n> > > - moved nonblock logic out of V4L2Camera and into V4L2CameraProxy\n> > > - add todos\n> > > - oter cosmetic changes\n> > >\n> > > Changes in v2:\n> > > - move all errno acrobatics to V4L2CameraProxy\n> > > - remove all mentions of dmabuf\n> > > - make V4L2CompatManager::getCamera return pointer rather than\n> > >   shared_ptr\n> > > - match V4L2 device nodes to libcamera cameras using Camera::name()\n> > >   compared to /sys/dev/char/maj:min/name (only works for UVC cameras)\n> > >   - in V4L2CompatManager::getCameraIndex()\n> > > - add -fvisibility=hidden to v4l2 compat\n> > > - cache the results of V4L2CompatManager::imageSize() within V4L2Camera\n> > >   (where V4L2Camera is interested)\n> > > - remove V4L2CompatManager::valid[fd|mmap], and where those methods were\n> > >   used, check V4L2CompatManager::getCamera() != nullptr instead\n> > > - fixed V4L2CompatManager::drmToV4L2() mappings for DRM_FORMAT_BGR888\n> > >   and DRM_FORMAT_RGB888\n> > > - other cosmetic changes\n> > > ---\n> > >  meson_options.txt                |   5 +\n> > >  src/meson.build                  |   4 +\n> > >  src/v4l2/meson.build             |  31 ++\n> > >  src/v4l2/v4l2_camera.cpp         | 248 ++++++++++++++++\n> > >  src/v4l2/v4l2_camera.h           |  84 ++++++\n> > >  src/v4l2/v4l2_camera_proxy.cpp   | 480 +++++++++++++++++++++++++++++++\n> > >  src/v4l2/v4l2_camera_proxy.h     |  70 +++++\n> > >  src/v4l2/v4l2_compat.cpp         |  78 +++++\n> > >  src/v4l2/v4l2_compat_manager.cpp | 379 ++++++++++++++++++++++++\n> > >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> > >  10 files changed, 1465 insertions(+)\n> > >  create mode 100644 src/v4l2/meson.build\n> > >  create mode 100644 src/v4l2/v4l2_camera.cpp\n> > >  create mode 100644 src/v4l2/v4l2_camera.h\n> > >  create mode 100644 src/v4l2/v4l2_camera_proxy.cpp\n> > >  create mode 100644 src/v4l2/v4l2_camera_proxy.h\n> > >  create mode 100644 src/v4l2/v4l2_compat.cpp\n> > >  create mode 100644 src/v4l2/v4l2_compat_manager.cpp\n> > >  create mode 100644 src/v4l2/v4l2_compat_manager.h\n> > >\n> > > diff --git a/meson_options.txt b/meson_options.txt\n> > > index 1a328045..b06dd494 100644\n> > > --- a/meson_options.txt\n> > > +++ b/meson_options.txt\n> > > @@ -10,3 +10,8 @@ option('documentation',\n> > >  option('test',\n> > >          type : 'boolean',\n> > >          description: 'Compile and include the tests')\n> > > +\n> > > +option('v4l2',\n> > > +        type : 'boolean',\n> > > +        value : false,\n> > > +        description : 'Compile libcamera with V4L2 compatibility layer')\n> \n> 'Compile the V4L2 compatibility layer'\n> \n> (I incorrectly told you to disregard my whole comment in v2, when I\n> meant disregarding the s/compatibility/adaptation/)\n> \n> > > diff --git a/src/meson.build b/src/meson.build\n> > > index 67ad20aa..5adcd61f 100644\n> > > --- a/src/meson.build\n> > > +++ b/src/meson.build\n> > > @@ -6,3 +6,7 @@ subdir('libcamera')\n> > >  subdir('ipa')\n> > >  subdir('cam')\n> > >  subdir('qcam')\n> > > +\n> > > +if get_option('v4l2')\n> > > +    subdir('v4l2')\n> > > +endif\n> > > diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build\n> > > new file mode 100644\n> > > index 00000000..14ee3594\n> > > --- /dev/null\n> > > +++ b/src/v4l2/meson.build\n> > > @@ -0,0 +1,31 @@\n> > > +v4l2_compat_sources = files([\n> > > +    'v4l2_camera.cpp',\n> > > +    'v4l2_camera_proxy.cpp',\n> > > +    'v4l2_compat.cpp',\n> > > +    'v4l2_compat_manager.cpp',\n> > > +])\n> > > +\n> > > +v4l2_compat_includes = [\n> > > +    libcamera_includes,\n> > > +    libcamera_internal_includes,\n> > > +]\n> > > +\n> > > +v4l2_compat_cpp_args = [\n> > > +    # Meson enables large file support unconditionally, which redirect file\n> > > +    # operations to 64-bit versions. This results in some symbols being\n> > > +    # renamed, for instance open() being renamed to open64(). As the V4L2\n> > > +    # adaptation wrapper needs to provide both 32-bit and 64-bit versions of\n> > > +    # file operations, disable transparent large file support.\n> > > +    '-U_FILE_OFFSET_BITS',\n> > > +    '-D_FILE_OFFSET_BITS=32',\n> > > +    '-fvisibility=hidden',\n> > > +]\n> > > +\n> > > +v4l2_compat = shared_library('v4l2-compat',\n> > > +                             v4l2_compat_sources,\n> > > +                             name_prefix : '',\n> > > +                             install : true,\n> > > +                             link_with : libcamera,\n> > > +                             include_directories : v4l2_compat_includes,\n> > > +                             dependencies : libcamera_deps,\n> > > +                             cpp_args : v4l2_compat_cpp_args)\n> > > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > > new file mode 100644\n> > > index 00000000..2d33be9f\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_camera.cpp\n> > > @@ -0,0 +1,248 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_camera.cpp - V4L2 compatibility camera\n> > > + */\n> > > +\n> > > +#include \"v4l2_camera.h\"\n> > > +\n> > > +#include <errno.h>\n> > > +#include <linux/videodev2.h>\n> > > +#include <sys/mman.h>\n> > > +#include <sys/syscall.h>\n> > > +#include <time.h>\n> > > +#include <unistd.h>\n> > > +\n> > > +#include \"log.h\"\n> > > +#include \"utils.h\"\n> > > +#include \"v4l2_compat_manager.h\"\n> > \n> > I think you can now drop this.\n> > \n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > > +\n> > > +FrameMetadata::FrameMetadata(Buffer *buffer)\n> > > +\t: index_(buffer->index()),\n> > > +\t  bytesused_(buffer->bytesused()),\n> > > +\t  timestamp_(buffer->timestamp()),\n> > > +\t  sequence_(buffer->sequence()),\n> > > +\t  status_(buffer->status())\n> > \n> > please span 80cols\n> > \n> > > +{\n> > > +}\n> > > +\n> > > +V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)\n> > > +\t: camera_(camera), bufferCount_(0), isRunning_(false)\n> > > +{\n> > > +\tcamera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);\n> > > +};\n> > \n> > s/};/}\n> > \n> > > +\n> > > +V4L2Camera::~V4L2Camera()\n> > > +{\n> > > +\tcamera_->release();\n> > \n> > Closing then destroying a V4L2Camera would cause a double release() ?\n> \n> It can, but I think that's fine, Camera::release() is a no-op in that\n> case.\n> \n> > > +}\n> > > +\n> > > +void V4L2Camera::open(int *ret)\n> > > +{\n> > > +\tif (camera_->acquire() < 0) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Failed to acquire camera\";\n> > > +\t\t*ret = -EINVAL;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\tconfig_ = camera_->generateConfiguration({ StreamRole::Viewfinder });\n> > > +\tif (!config_) {\n> > > +\t\tcamera_->release();\n> > > +\t\t*ret = -EINVAL;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\t*ret = 0;\n> > > +}\n> > > +\n> > > +void V4L2Camera::close(int *ret)\n> > > +{\n> > > +\t*ret = camera_->release();\n> > > +}\n> > > +\n> > > +void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)\n> > > +{\n> > > +\t*streamConfig = config_->at(0);\n> > > +}\n> > > +\n> > > +void V4L2Camera::requestComplete(Request *request)\n> > > +{\n> > > +\tif (request->status() == Request::RequestCancelled)\n> > > +\t\treturn;\n> > > +\n> > > +\t/* We only have one stream at the moment. */\n> > > +\tbufferLock_.lock();\n> > > +\tBuffer *buffer = request->buffers().begin()->second;\n> > > +\tstd::unique_ptr<FrameMetadata> fmd =\n> \n> s/fmd/metadata/ ?\n> \n> > > +\t\tutils::make_unique<FrameMetadata>(buffer);\n> > > +\tcompletedBuffers_.push(std::move(fmd));\n> > > +\tbufferLock_.unlock();\n> > > +\n> > > +\tbufferSema_.release();\n> > > +}\n> > > +\n> > > +void V4L2Camera::configure(int *ret, StreamConfiguration *streamConfigOut,\n> > > +\t\t\t   Size *size, PixelFormat pixelformat,\n> > > +\t\t\t   unsigned int bufferCount)\n> > > +{\n> > > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > > +\tstreamConfig.size.width = size->width;\n> > > +\tstreamConfig.size.height = size->height;\n> > > +\tstreamConfig.pixelFormat = pixelformat;\n> > > +\tbufferCount_ = bufferCount;\n> > > +\tstreamConfig.bufferCount = bufferCount_;\n> > > +\t/* \\todo memoryType (interval vs external) */\n> > > +\n> > > +\tCameraConfiguration::Status validation = config_->validate();\n> > > +\tif (validation == CameraConfiguration::Invalid) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Configuration invalid\";\n> > > +\t\t*ret = -EINVAL;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\tif (validation == CameraConfiguration::Adjusted)\n> > > +\t\tLOG(V4L2Compat, Info) << \"Configuration adjusted\";\n> > > +\n> > > +\tLOG(V4L2Compat, Info) << \"Validated configuration is: \"\n> > > +\t\t\t      << streamConfig.toString();\n> > > +\n> > > +\t*ret = camera_->configure(config_.get());\n> > > +\tif (*ret < 0)\n> > > +\t\treturn;\n> > > +\n> > > +\tbufferCount_ = streamConfig.bufferCount;\n> > > +\n> > > +\t*streamConfigOut = config_->at(0);\n> > > +}\n> > > +\n> > > +void V4L2Camera::mmap(void **ret, unsigned int index)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> > > +\n> > > +\tStream *stream = *camera_->streams().begin();\n> > > +\t*ret = stream->buffers()[index].planes()[0].mem();\n> > \n> > Should we check if index is not out of buffers() array bounds ?\n> \n> This is already checked by the caller.\n> \n> > > +}\n> > > +\n> > > +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> > > +\n> > > +\t*ret = camera_->allocateBuffers();\n> > > +\tif (*ret == -EACCES)\n> > > +\t\t*ret = -EBUSY;\n> > > +}\n> > > +\n> > > +void V4L2Camera::freeBuffers()\n> > > +{\n> > > +\tcamera_->freeBuffers();\n> > > +\tbufferCount_ = 0;\n> > \n> > is bufferCount_ used at all ?\n> \n> Not anymore in this class, it's probably a leftover.\n> \n> > > +}\n> > > +\n> > > +void V4L2Camera::streamOn(int *ret)\n> > > +{\n> > > +\t*ret = 0;\n> > > +\n> > > +\tif (isRunning_)\n> > > +\t\treturn;\n> > > +\n> > > +\t*ret = camera_->start();\n> > > +\tif (*ret < 0) {\n> > > +\t\tif (*ret == -EACCES)\n> > > +\t\t\t*ret = -EBUSY;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\tisRunning_ = true;\n> > > +\n> > > +\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > > +\t\t/* \\todo What should we do if this returns -EINVAL? */\n> \n> Good point, we'll have to implement better error handling here. Thanks\n> for capturing it.\n> \n> > > +\t\t*ret = camera_->queueRequest(req.release());\n> > > +\t\tif (*ret < 0) {\n> > > +\t\t\t*ret = (*ret == -EACCES ? -EAGAIN : *ret);\n> > > +\t\t\treturn;\n> > > +\t\t}\n> > > +\t}\n> > > +\n> > > +\tpendingRequests_.clear();\n> > > +}\n> > > +\n> > > +void V4L2Camera::streamOff(int *ret)\n> > > +{\n> > > +\t*ret = 0;\n> > > +\n> > > +\t/* \\todo restore buffers to reqbufs state? */\n> > > +\tif (!isRunning_)\n> > > +\t\treturn;\n> > > +\n> > > +\t*ret = camera_->stop();\n> > > +\tif (*ret < 0) {\n> > > +\t\tif (*ret == -EACCES)\n> > > +\t\t\t*ret = -EBUSY;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\tisRunning_ = false;\n> > > +}\n> > > +\n> > > +void V4L2Camera::qbuf(int *ret, unsigned int index)\n> > > +{\n> > > +\tStream *stream = config_->at(0).stream();\n> > > +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(index);\n> > > +\tif (!buffer) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Can't create buffer\";\n> > > +\t\t*ret = -ENOMEM;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\tstd::unique_ptr<Request> request =\n> > > +\t\tstd::unique_ptr<Request>(camera_->createRequest());\n> > > +\tif (!request) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > > +\t\t*ret = -ENOMEM;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\t*ret = request->addBuffer(std::move(buffer));\n> > > +\tif (*ret < 0) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Can't set buffer for request\";\n> > > +\t\t*ret = -ENOMEM;\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\tif (!isRunning_) {\n> > > +\t\tpendingRequests_.push_back(std::move(request));\n> > > +\t} else {\n> > > +\t\t*ret = camera_->queueRequest(request.release());\n> > > +\t\tif (*ret < 0) {\n> > > +\t\t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> > > +\t\t\tif (*ret == -EACCES)\n> > > +\t\t\t\t*ret = -EBUSY;\n> > > +\t\t\treturn;\n> > > +\t\t}\n> > > +\t}\n> > > +\n> > > +\t*ret = 0;\n> > > +}\n> > > +\n> > > +int V4L2Camera::dqbuf(struct v4l2_buffer *arg, bool nonblock)\n> > > +{\n> > > +\tif (nonblock && !bufferSema_.tryAcquire())\n> > > +\t\treturn -EAGAIN;\n> > > +\telse\n> > > +\t\tbufferSema_.acquire();\n> > > +\n> > > +\tbufferLock_.lock();\n> > > +\tFrameMetadata *fmd = completedBuffers_.front().get();\n> > > +\tcompletedBuffers_.pop();\n> > > +\tbufferLock_.unlock();\n> > > +\n> > > +\targ->bytesused = fmd->bytesused();\n> > > +\targ->field = V4L2_FIELD_NONE;\n> > > +\targ->timestamp.tv_sec = fmd->timestamp() / 1000000000;\n> > > +\targ->timestamp.tv_usec = fmd->timestamp() % 1000000;\n> > > +\targ->sequence = fmd->sequence();\n> > \n> > What about the buffer index ?\n> \n> It's set by the caller.\n> \n> I'll review the qbuf/dqbuf strategy on patch 6/6.\n> \n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > > new file mode 100644\n> > > index 00000000..13418b6b\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_camera.h\n> > > @@ -0,0 +1,84 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_camera.h - V4L2 compatibility camera\n> > > + */\n> > > +\n> > > +#ifndef __V4L2_CAMERA_H__\n> > > +#define __V4L2_CAMERA_H__\n> > > +\n> > > +#include <deque>\n> > > +#include <linux/videodev2.h>\n> > > +#include <mutex>\n> > > +#include <queue>\n> > > +\n> > > +#include <libcamera/buffer.h>\n> > > +#include <libcamera/camera.h>\n> > > +#include \"semaphore.h\"\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +class FrameMetadata\n> > > +{\n> > > +public:\n> > > +\tFrameMetadata(Buffer *buffer);\n> > > +\n> > > +\tint index() const { return index_; }\n> > > +\n> > > +\tunsigned int bytesused() const { return bytesused_; }\n> > > +\tuint64_t timestamp() const { return timestamp_; }\n> > > +\tunsigned int sequence() const { return sequence_; }\n> > > +\n> > > +\tBuffer::Status status() const { return status_; }\n> > > +\n> > > +private:\n> > > +\tint index_;\n> > > +\n> > > +\tunsigned int bytesused_;\n> > > +\tuint64_t timestamp_;\n> > > +\tunsigned int sequence_;\n> > > +\n> > > +\tBuffer::Status status_;\n> > > +};\n> > > +\n> > > +class V4L2Camera : public Object\n> > > +{\n> > > +public:\n> > > +\tV4L2Camera(std::shared_ptr<Camera> camera);\n> > > +\t~V4L2Camera();\n> > > +\n> > > +\tvoid open(int *ret);\n> > > +\tvoid close(int *ret);\n> > > +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> > > +\n> > > +\tvoid mmap(void **ret, unsigned int index);\n> > > +\n> > > +\tvoid configure(int *ret, StreamConfiguration *streamConfigOut, Size *size,\n> > > +\t\t       PixelFormat pixelformat, unsigned int bufferCount);\n> > > +\n> > > +\tvoid allocBuffers(int *ret, unsigned int count);\n> > > +\tvoid freeBuffers();\n> > > +\tvoid streamOn(int *ret);\n> > > +\tvoid streamOff(int *ret);\n> > > +\n> > > +\tvoid qbuf(int *ret, unsigned int index);\n> > > +\tint dqbuf(struct v4l2_buffer *arg, bool nonblock);\n> > > +\n> > > +private:\n> > > +\tvoid requestComplete(Request *request);\n> > > +\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tstd::unique_ptr<CameraConfiguration> config_;\n> > > +\n> > > +\tunsigned int bufferCount_;\n> > > +\tbool isRunning_;\n> > > +\n> > > +\tSemaphore bufferSema_;\n> > > +\tstd::mutex bufferLock_;\n> > > +\n> > > +\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > > +\tstd::queue<std::unique_ptr<FrameMetadata>> completedBuffers_;\n> > > +};\n> > > +\n> > > +#endif /* __V4L2_CAMERA_H__ */\n> > > diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp\n> > > new file mode 100644\n> > > index 00000000..b0acd477\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> > > @@ -0,0 +1,480 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera\n> > > + */\n> > > +\n> > > +#include \"v4l2_camera_proxy.h\"\n> > > +\n> > > +#include <algorithm>\n> > > +#include <linux/videodev2.h>\n> > > +#include <string.h>\n> > > +#include <sys/mman.h>\n> > > +\n> > > +#include <libcamera/camera.h>\n> > > +#include <libcamera/object.h>\n> > > +\n> > > +#include \"log.h\"\n> > > +#include \"utils.h\"\n> > > +#include \"v4l2_camera.h\"\n> > > +#include \"v4l2_compat_manager.h\"\n> > > +\n> > > +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > > +\n> > > +V4L2CameraProxy::V4L2CameraProxy(unsigned int index,\n> > > +\t\t\t\t std::shared_ptr<Camera> camera)\n> > > +\t: index_(index), bufferCount_(0), currentBuf_(0),\n> > > +\t  vcam_(utils::make_unique<V4L2Camera>(camera))\n> \n> You need to initialize refcount_ to 0.\n> \n> > > +{\n> > > +\tquerycap(camera);\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::open(bool nonBlocking)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing open\";\n> > > +\n> > > +\tint ret;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> > > +\t\t\t    &ret);\n> > > +\tif (ret < 0) {\n> > > +\t\terrno = -ret;\n> > > +\t\treturn -1;\n> > > +\t}\n> > > +\n> > > +\tnonBlocking_ = nonBlocking;\n> > > +\n> > > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > > +\tsetFmtFromConfig(streamConfig_);\n> > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > \n> > sizeimage_ could be 0, which means the default format is not supported\n> > by our compat layer. Can this cause troubles later?\n> \n> Potentially, but that should not happen as the compat layer should\n> support all formats supported by libcamera. I wonder if we could\n> somehow enforce this at compilation time. Moving the DRM <-> V4L2\n> format conversion functions to the libcamera core could help in this\n> area. For now I think we can ignore the issue.\n> \n> > > +\n> > > +\trefcount_++;\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +void V4L2CameraProxy::dup()\n> > > +{\n> > > +\trefcount_++;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::close()\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing close\";\n> > > +\n> > > +\tif (refcount_ > 1) {\n> > > +\t\trefcount_--;\n> \n> You need to decrement refcount_ even if == 1. I would thus write\n> \n> \tif (--refcount_ > 0)\n> \t\treturn 0;\n> \n> > > +\t\treturn 0;\n> > > +\t}\n> > \n> > Should this be locked ?\n> \n> Eventually, but it's not the only thing that needs to be locked. It just\n> occurred to me that we are very thread-unsafe if the application using\n> the compat layer is threaded. That's OK, one step at a time :-) Let's\n> just remember about it.\n> \n> > > +\n> > > +\tint ret;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::close, ConnectionTypeBlocking, &ret);\n> > > +\tif (ret < 0) {\n> > > +\t\terrno = EIO;\n> > > +\t\treturn -1;\n> > > +\t}\n> \n> This ends up calling Camera::release() and forwarding the error up, and\n> the only possible cause of error is an invalid state of the camera,\n> which would result from a bug in the V4L2 adaptation layer. Let's\n> simplify the code (saving us from handling refcount_ specially in case\n> of close() errors) and avoid dealing with close() errors by making this\n> method and V4L2Camera::close() void.\n> \n> > > +\n> > > +\treturn ret;\n> > > +}\n> > > +\n> > > +void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,\n> > > +\t\t\t    off_t offset)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing mmap\";\n> > > +\n> > > +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> > > +\t\terrno = ENOTSUP;\n> > > +\t\treturn MAP_FAILED;\n> > > +\t}\n> > > +\n> > > +\tunsigned int index = offset / sizeimage_;\n> > > +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> > > +\t\terrno = EINVAL;\n> > > +\t\treturn MAP_FAILED;\n> > > +\t}\n> > > +\n> > > +\tvoid *val;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> > > +\t\t\t    &val, index);\n> > > +\treturn val;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing munmap\";\n> > > +\n> > > +\tif (length != sizeimage_) {\n> > > +\t\terrno = EINVAL;\n> > > +\t\treturn -1;\n> > > +\t}\n> \n> We should probably also keep a list of mapped addresses and check that\n> addr is in that list. This can be left for later, but please add a \\todo\n> item in that case.\n> \n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +/* \\todo getDeviceCaps? getMemoryCaps? */\n> \n> What is this ?\n> \n> > > +\n> > > +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> > > +{\n> > > +\treturn (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> \n> No need for parentheses.\n> \n> > > +}\n> > > +\n> > > +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> > > +{\n> > > +\treturn (memory == V4L2_MEMORY_MMAP);\n> \n> Same here.\n> \n> > > +}\n> > > +\n> > > +void V4L2CameraProxy::setFmtFromConfig(StreamConfiguration &streamConfig)\n> > > +{\n> > > +\tcurV4L2Format_.fmt.pix.width = streamConfig.size.width;\n> > > +\tcurV4L2Format_.fmt.pix.height = streamConfig.size.height;\n> > > +\tcurV4L2Format_.fmt.pix.pixelformat =\n> > > +\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat);\n> > > +\tcurV4L2Format_.fmt.pix.field = V4L2_FIELD_NONE;\n> > > +\tcurV4L2Format_.fmt.pix.bytesperline =\n> > > +\t\tV4L2CompatManager::bplMultiplier(\n> > > +\t\t\tcurV4L2Format_.fmt.pix.pixelformat) *\n> > > +\t\tcurV4L2Format_.fmt.pix.width;\n> > > +\tcurV4L2Format_.fmt.pix.sizeimage =\n> > > +\t\tV4L2CompatManager::imageSize(curV4L2Format_.fmt.pix.pixelformat,\n> > > +\t\t\t\t\t     curV4L2Format_.fmt.pix.width,\n> > > +\t\t\t\t\t     curV4L2Format_.fmt.pix.height);\n> > > +\tcurV4L2Format_.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;\n> > > +}\n> > > +\n> > > +unsigned int V4L2CameraProxy::calculateSizeImage(StreamConfiguration &streamConfig)\n> > > +{\n> > > +\treturn V4L2CompatManager::imageSize(\n> > > +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> > > +\t\t\tstreamConfig.size.width,\n> > > +\t\t\tstreamConfig.size.height);\n> > > +}\n> > > +\n> > > +void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)\n> \n> This is an instance method, no need to pass camera as an argument, you\n> can use vcam_.\n\nI need access to camera for camera->name()... I could extract it in the\nconstructor and pass it in as an argument maybe? (too late, as I just\nsent v4, though I prepared the below reply before)\n\nIn any case, I can't get it from vcam_->camera_, since that's private,\nand I was under the impression that we didn't want friend classes :/\n\nAlso this entire method looked a bit bulky to put into the constructor.\n\n> > > +{\n> > > +\tstd::string driver = \"libcamera\";\n> > > +\tstd::string bus_info = driver + \":\" + std::to_string(index_);\n> > > +\n> > > +\tmemcpy(capabilities_.driver, driver.c_str(),\n> > > +\t       sizeof(capabilities_.driver));\n> > > +\tmemcpy(capabilities_.card, camera->name().c_str(),\n> > > +\t       sizeof(capabilities_.card));\n> > > +\tmemcpy(capabilities_.bus_info, bus_info.c_str(),\n> > > +\t       sizeof(capabilities_.bus_info));\n> > > +\t/* \\todo Put this is header/config somewhere. */\n> \n> s/is/in/\n> \n> > > +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> > > +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> > > +\tcapabilities_.capabilities =\n> > > +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\n> > \n> > Nit:\n> > \tcapabilities_.capabilities = capabilities_.device_caps |\n> > \t\t\t\t     V4L2_CAP_DEVICE_CAPS;\n> \n>  \tcapabilities_.capabilities = capabilities_.device_caps\n>  \t\t\t\t   | V4L2_CAP_DEVICE_CAPS;\n> \n> :-)\n> \n> > > +\tmemset(capabilities_.reserved, 0, sizeof(capabilities_.reserved));\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querycap\";\n> > > +\n> > > +\tmemcpy(arg, &capabilities_, sizeof(*arg));\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_enum_fmt\";\n> > > +\n> > > +\tif (!validateStreamType(arg->type) ||\n> > > +\t    arg->index > streamConfig_.formats().pixelformats().size())\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\t/* \\todo Add map from format to description. */\n> > > +\tmemcpy(arg->description, \"asdf\", 5);\n> > \n> > Could you at least provide a temporary name a little more meaningful ?\n> \n> Agreed.\n> \n> > > +\targ->pixelformat = V4L2CompatManager::drmToV4L2(\n> > > +\t\t\t\tstreamConfig_.formats().pixelformats()[arg->index]);\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_g_fmt\";\n> > > +\n> > > +\tif (!validateStreamType(arg->type))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\targ->fmt.pix.width        = curV4L2Format_.fmt.pix.width;\n> > > +\targ->fmt.pix.height       = curV4L2Format_.fmt.pix.height;\n> > > +\targ->fmt.pix.pixelformat  = curV4L2Format_.fmt.pix.pixelformat;\n> > > +\targ->fmt.pix.field        = curV4L2Format_.fmt.pix.field;\n> > > +\targ->fmt.pix.bytesperline = curV4L2Format_.fmt.pix.bytesperline;\n> > > +\targ->fmt.pix.sizeimage    = curV4L2Format_.fmt.pix.sizeimage;\n> > > +\targ->fmt.pix.colorspace   = curV4L2Format_.fmt.pix.colorspace;\n> > \n> > Nit:\n> > Couldn't you just assign arg->fmt.pix = curV4L2Format_.fmt.pix ?\n> > It will make it easier to support multiplanar ?\n> \n> v4l2_pix_format doesn't contain pointers, so this should be sage and\n> will simplify the code. Note that we should likely\n> \n> \tmemset(&arg->fmt, 0, sizeof(arg->fmt));\n> \n> before the assignment.\n> \n> It will be interesting to run v4l2-compliance on this :-)\n> \n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_s_fmt\";\n> > > +\n> > > +\tint ret = vidioc_try_fmt(arg);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > > +\t\t\t    &streamConfig_,\n> > > +\t\t\t    new Size(arg->fmt.pix.width, arg->fmt.pix.height),\n> \n> You're leaking the newly allocate Size. Why don't you pass a const Size\n> & instead of a Size * ? Were you trying to work around a compilation\n> error ? :-)\n> \n> > > +\t\t\t    V4L2CompatManager::v4l2ToDrm(arg->fmt.pix.pixelformat),\n> > > +\t\t\t    bufferCount_);\n> > > +\tif (ret < 0)\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > > +\tif (sizeimage_ == 0)\n> > > +\t\treturn -EINVAL;\n> \n> A VIDIOC_S_FMT error shouldn't change the active configuration of the\n> device. You thus need to work on a local copy of sizeimage_.\n> \n> > > +\n> > > +\tsetFmtFromConfig(streamConfig_);\n> \n> You always call calculateSizeImage() and setFmtFromConfig() together,\n> and calculateSizeImage() actually duplicates some code from\n> setFmtFromConfig(). I would thus remove calculateSizeImage() and turn\n> setFmtFromConfig() into an int function that will not perform any change\n> to the active config if an error has to be returned. You could then\n> remove sizeimage_ and replace it with curV4L2Format_.fmt.pix.sizeimage\n> (with appropriate local variables if needed to keep the code readable).\n\nI've decided to keep them separate, because setFmtFromConfig does\ninitialization in V4L2CameraProxy::open(), where sizeimage (until we fix\nv4l2 <-> drm conversions) may not be valid until we s_fmt/configure,\nbut we still want the curV4L2Format_ to be populated.\n\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_try_fmt\";\n> > > +\tif (!validateStreamType(arg->type))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tunsigned int format = arg->fmt.pix.pixelformat;\n> > > +\tconst std::vector<PixelFormat> &formats =\n> > > +\t\tstreamConfig_.formats().pixelformats();\n> > > +\tif (std::find(formats.begin(), formats.end(), format) == formats.end())\n> > > +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> > > +\n> > > +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> > > +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> > > +\tif (std::find(sizes.begin(), sizes.end(), size) == sizes.end())\n> > > +\t\tsize = streamConfig_.formats().sizes(format)[0];\n> > > +\n> > > +\targ->fmt.pix.width        = size.width;\n> > > +\targ->fmt.pix.height       = size.height;\n> > > +\targ->fmt.pix.pixelformat  = format;\n> > > +\targ->fmt.pix.field        = V4L2_FIELD_NONE;\n> > > +\targ->fmt.pix.bytesperline =\n> > > +\t\tV4L2CompatManager::bplMultiplier(\n> > > +\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> > > +\t\targ->fmt.pix.width;\n> > > +\targ->fmt.pix.sizeimage    =\n> > > +\t\tV4L2CompatManager::imageSize(\n> > > +\t\t\tV4L2CompatManager::drmToV4L2(format),\n> > > +\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n> >\n> > Nit:\n> > \n> > \targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n> > \t\t\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> > \t\t\t\t\targ->fmt.pix.width;\n> > \targ->fmt.pix.sizeimage    = V4L2CompatManager::imageSize(\n> > \t\t\t\t\tV4L2CompatManager::drmToV4L2(format),\n> > \t\t\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n> \n> I'm not sure it's much easier to read. The good news is that moving\n> bplMultiplier() and imageSize() to the V4L2CameraProxy class (as\n> explained below) will help.\n> \n> > > +\targ->fmt.pix.colorspace   = V4L2_COLORSPACE_SRGB;\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)\n> > > +{\n> > > +\tint ret;\n> > > +\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_reqbufs\";\n> > > +\tif (!validateStreamType(arg->type) ||\n> > > +\t    !validateMemoryType(arg->memory))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tLOG(V4L2Compat, Debug) << arg->count << \" bufs requested \";\n> > > +\n> > > +\targ->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;\n> > > +\n> > > +\tif (arg->count == 0) {\n> > > +\t\tLOG(V4L2Compat, Debug) << \"Freeing libcamera bufs\";\n> > > +\t\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > > +\t\t\t\t    ConnectionTypeBlocking, &ret);\n> > > +\t\tif (ret < 0) {\n> > > +\t\t\tLOG(V4L2Compat, Error) << \"Failed to stop stream\";\n> > > +\t\t\treturn ret;\n> > > +\t\t}\n> > > +\t\tvcam_->invokeMethod(&V4L2Camera::freeBuffers,\n> > > +\t\t\t\t    ConnectionTypeBlocking);\n> > > +\t\tbufferCount_ = 0;\n> > > +\t\treturn 0;\n> > > +\t}\n> > > +\n> > > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > > +\t\t\t    &streamConfig_,\n> > > +\t\t\t    new Size(curV4L2Format_.fmt.pix.width,\n> > > +\t\t\t\t     curV4L2Format_.fmt.pix.height),\n> > > +\t\t\t    V4L2CompatManager::v4l2ToDrm(curV4L2Format_.fmt.pix.pixelformat),\n> > > +\t\t\t    arg->count);\n> > > +\tif (ret < 0)\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > > +\tif (sizeimage_ == 0)\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tsetFmtFromConfig(streamConfig_);\n> > \n> > Do we need to go through configure() again ?\n> \n> I was going to ask the same :-) It is valid for a V4L2 application to\n> open a device and call VIDIOC_REQBUFS, so we need to handle that.\n> Calling configure() here is an option, but maybe setting an initial\n> configuration at open() time would be better.\n\nWe need configure() again here because configuring the number of buffers\nin libcamera is done via configure(), while in V4L2 it is done via\nreqbufs, separate from s_fmt. This is also why we have bufferCount_.\n\n\nThanks,\n\nPaul\n\n> > > +\n> > > +\targ->count = streamConfig_.bufferCount;\n> > > +\tbufferCount_ = arg->count;\n> > > +\n> > > +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> > > +\t\treturn -EINVAL;\n> > \n> > I would fail earlier in this case.\n> > Ah wait, you do already, don't you?\n> \n> Good catch :-)\n> \n> > > +\n> > > +\tvcam_->invokeMethod(&V4L2Camera::allocBuffers,\n> > > +\t\t\t    ConnectionTypeBlocking, &ret, arg->count);\n> > > +\tif (ret < 0) {\n> > > +\t\targ->count = 0;\n> > > +\t\treturn ret == -EACCES ? -EBUSY : ret;\n> > > +\t}\n> > > +\n> > > +\tLOG(V4L2Compat, Debug) << \"Allocated \" << arg->count << \" buffers\";\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querybuf\";\n> > > +\tStream *stream = streamConfig_.stream();\n> > > +\n> > > +\tif (!validateStreamType(arg->type) ||\n> > > +\t    arg->index >= stream->buffers().size())\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tunsigned int index = arg->index;\n> > > +\tmemset(arg, 0, sizeof(*arg));\n> > > +\targ->index = index;\n> > > +\targ->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;\n> > > +\targ->length = curV4L2Format_.fmt.pix.sizeimage;\n> > > +\targ->memory = V4L2_MEMORY_MMAP;\n> > > +\targ->m.offset = arg->index * curV4L2Format_.fmt.pix.sizeimage;\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_qbuf, index = \"\n> > > +\t\t\t       << arg->index;\n> > > +\n> > > +\tStream *stream = streamConfig_.stream();\n> > > +\n> > > +\tif (!validateStreamType(arg->type) ||\n> > > +\t    !validateMemoryType(arg->memory) ||\n> > > +\t    arg->index >= stream->buffers().size())\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tint ret;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::qbuf, ConnectionTypeBlocking,\n> > > +\t\t\t    &ret, arg->index);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> > > +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> \n> The V4L2_BUF_FLAG_MAPPED flag should be set at mmap() time and cleared\n> an munmap() time. It shouldn't be touched here.\n> \n> > > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> > > +\n> > > +\treturn ret;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_dqbuf\";\n> > > +\n> > > +\tif (!validateStreamType(arg->type) ||\n> > > +\t    !validateMemoryType(arg->memory))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\targ->index = currentBuf_;\n> > > +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> > > +\n> > > +\tint ret = vcam_->dqbuf(arg, nonBlocking_);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> > > +\targ->flags |= V4L2_BUF_FLAG_DONE;\n> > > +\n> > > +\targ->length = sizeimage_;\n> > > +\n> > > +\treturn ret;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_streamon(int *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamon\";\n> > > +\n> > > +\tif (!validateStreamType(*arg))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tint ret;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::streamOn,\n> > > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > > +\treturn ret;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::vidioc_streamoff(int *arg)\n> > > +{\n> > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamoff\";\n> > > +\n> > > +\tif (!validateStreamType(*arg))\n> > > +\t\treturn -EINVAL;\n> > > +\n> > > +\tint ret;\n> > > +\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > > +\treturn ret;\n> > > +}\n> > > +\n> > > +int V4L2CameraProxy::ioctl(unsigned long request, void *arg)\n> > > +{\n> > > +\tint ret;\n> > > +\tswitch (request) {\n> > > +\tcase VIDIOC_QUERYCAP:\n> > > +\t\tret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_ENUM_FMT:\n> > > +\t\tret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_G_FMT:\n> > > +\t\tret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_S_FMT:\n> > > +\t\tret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_TRY_FMT:\n> > > +\t\tret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_REQBUFS:\n> > > +\t\tret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_QUERYBUF:\n> > > +\t\tret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_QBUF:\n> > > +\t\tret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_DQBUF:\n> > > +\t\tret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_STREAMON:\n> > > +\t\tret = vidioc_streamon(static_cast<int *>(arg));\n> > > +\t\tbreak;\n> > > +\tcase VIDIOC_STREAMOFF:\n> > > +\t\tret = vidioc_streamoff(static_cast<int *>(arg));\n> > > +\t\tbreak;\n> > > +\tdefault:\n> > > +\t\tret = -ENOTTY;\n> > > +\t\tbreak;\n> > > +\t}\n> > > +\n> > > +\tif (ret < 0) {\n> > > +\t\terrno = -ret;\n> > > +\t\treturn -1;\n> > > +\t}\n> > > +\n> > > +\treturn ret;\n> > > +}\n> > > diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h\n> > > new file mode 100644\n> > > index 00000000..51fdbe19\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_camera_proxy.h\n> > > @@ -0,0 +1,70 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera\n> > > + */\n> > > +\n> > > +#ifndef __V4L2_CAMERA_PROXY_H__\n> > > +#define __V4L2_CAMERA_PROXY_H__\n> > > +\n> > > +#include <linux/videodev2.h>\n> > > +#include <memory>\n> > > +#include <sys/types.h>\n> > > +\n> > > +#include <libcamera/camera.h>\n> > > +\n> > > +#include \"v4l2_camera.h\"\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +class V4L2CameraProxy\n> > > +{\n> > > +public:\n> > > +\tV4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);\n> > > +\n> > > +\tint open(bool nonBlocking);\n> > > +\tvoid dup();\n> > > +\tint close();\n> > > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > > +\t\t   off_t offset);\n> > > +\tint munmap(void *addr, size_t length);\n> > > +\n> > > +\tint ioctl(unsigned long request, void *arg);\n> > > +\n> > > +\tunsigned int refcount() { return refcount_; }\n> \n> This method is not used, you can drop it.\n> \n> > > +\n> > > +private:\n> > > +\tbool validateStreamType(uint32_t type);\n> > > +\tbool validateMemoryType(uint32_t memory);\n> > > +\tvoid setFmtFromConfig(StreamConfiguration &streamConfig);\n> > > +\tunsigned int calculateSizeImage(StreamConfiguration &streamConfig);\n> > > +\tvoid querycap(std::shared_ptr<Camera> camera);\n> > > +\n> > > +\tint vidioc_querycap(struct v4l2_capability *arg);\n> > > +\tint vidioc_enum_fmt(struct v4l2_fmtdesc *arg);\n> > > +\tint vidioc_g_fmt(struct v4l2_format *arg);\n> > > +\tint vidioc_s_fmt(struct v4l2_format *arg);\n> > > +\tint vidioc_try_fmt(struct v4l2_format *arg);\n> > > +\tint vidioc_reqbufs(struct v4l2_requestbuffers *arg);\n> > > +\tint vidioc_querybuf(struct v4l2_buffer *arg);\n> > > +\tint vidioc_qbuf(struct v4l2_buffer *arg);\n> > > +\tint vidioc_dqbuf(struct v4l2_buffer *arg);\n> > > +\tint vidioc_streamon(int *arg);\n> > > +\tint vidioc_streamoff(int *arg);\n> > > +\n> > > +\tunsigned int refcount_;\n> > > +\tunsigned int index_;\n> > > +\tbool nonBlocking_;\n> > > +\n> > > +\tstruct v4l2_format curV4L2Format_;\n> > > +\tStreamConfiguration streamConfig_;\n> > > +\tstruct v4l2_capability capabilities_;\n> > > +\tunsigned int bufferCount_;\n> > > +\tunsigned int currentBuf_;\n> > > +\tunsigned int sizeimage_;\n> > > +\n> > > +\tstd::unique_ptr<V4L2Camera> vcam_;\n> > > +};\n> > > +\n> > > +#endif /* __V4L2_CAMERA_PROXY_H__ */\n> > > diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> > > new file mode 100644\n> > > index 00000000..a6a7aed2\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_compat.cpp\n> > > @@ -0,0 +1,78 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_compat.cpp - V4L2 compatibility layer\n> > > + */\n> > > +\n> > > +#include \"v4l2_compat_manager.h\"\n> > > +\n> > > +#include <errno.h>\n> > > +#include <fcntl.h>\n> > > +#include <stdarg.h>\n> > > +#include <sys/mman.h>\n> > > +#include <sys/stat.h>\n> > > +#include <sys/types.h>\n> > > +\n> > > +#define LIBCAMERA_PUBLIC __attribute__((visibility(\"default\")))\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +#define extract_va_arg(type, arg, last)\t\\\n> > > +{\t\t\t\t\t\\\n> > > +\tva_list ap;\t\t\t\\\n> > > +\tva_start(ap, last);\t\t\\\n> > > +\targ = va_arg(ap, type);\t\t\\\n> > > +\tva_end(ap);\t\t\t\\\n> > > +}\n> > > +\n> > > +extern \"C\" {\n> > > +\n> > > +LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)\n> > > +{\n> > > +\tmode_t mode = 0;\n> > > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > > +\t\textract_va_arg(mode_t, mode, oflag);\n> > > +\n> > > +\treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag, mode);\n> \n> Could you wrap lines at a 80 columns boundary when feasible ?\n> \n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)\n> > > +{\n> > > +\tmode_t mode = 0;\n> > > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > > +\t\textract_va_arg(mode_t, mode, oflag);\n> > > +\n> > > +\treturn V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);\n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC int dup(int oldfd)\n> > > +{\n> > > +\treturn V4L2CompatManager::instance()->dup(oldfd);\n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC int close(int fd)\n> > > +{\n> > > +\treturn V4L2CompatManager::instance()->close(fd);\n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,\n> > > +\t\t\t    int fd, off_t offset)\n> > > +{\n> > > +\treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> \n> Same comment here.\n> \n> It's annoying that clang-format doesn't implement a soft boundary and a\n> hard boundary.\n> \n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)\n> > > +{\n> > > +\treturn V4L2CompatManager::instance()->munmap(addr, length);\n> > > +}\n> > > +\n> > > +LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)\n> > > +{\n> > > +\tvoid *arg;\n> > > +\textract_va_arg(void *, arg, request);\n> > > +\n> > > +\treturn V4L2CompatManager::instance()->ioctl(fd, request, arg);\n> > > +}\n> > > +\n> > > +}\n> > > diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp\n> > > new file mode 100644\n> > > index 00000000..bfe76e82\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > > @@ -0,0 +1,379 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_compat_manager.cpp - V4L2 compatibility manager\n> > > + */\n> > > +\n> > > +#include \"v4l2_compat_manager.h\"\n> > > +\n> > > +#include <dlfcn.h>\n> > > +#include <fcntl.h>\n> > > +#include <fstream>\n> > > +#include <linux/drm_fourcc.h>\n> > > +#include <linux/videodev2.h>\n> > > +#include <map>\n> > > +#include <stdarg.h>\n> > > +#include <string.h>\n> > > +#include <sys/eventfd.h>\n> > > +#include <sys/mman.h>\n> > > +#include <sys/stat.h>\n> > > +#include <sys/sysmacros.h>\n> > > +#include <sys/types.h>\n> > > +#include <unistd.h>\n> > > +\n> > > +#include <libcamera/camera.h>\n> > > +#include <libcamera/camera_manager.h>\n> > > +#include <libcamera/stream.h>\n> > > +\n> > > +#include \"log.h\"\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +LOG_DEFINE_CATEGORY(V4L2Compat)\n> > > +\n> > > +V4L2CompatManager::V4L2CompatManager()\n> > > +\t: cm_(nullptr), initialized_(false)\n> > > +{\n> > > +\topenat_func_ = (openat_func_t)dlsym(RTLD_NEXT, \"openat\");\n> > > +\tdup_func_    = (dup_func_t   )dlsym(RTLD_NEXT, \"dup\");\n> > > +\tclose_func_  = (close_func_t )dlsym(RTLD_NEXT, \"close\");\n> > > +\tioctl_func_  = (ioctl_func_t )dlsym(RTLD_NEXT, \"ioctl\");\n> > > +\tmmap_func_   = (mmap_func_t  )dlsym(RTLD_NEXT, \"mmap\");\n> > > +\tmunmap_func_ = (munmap_func_t)dlsym(RTLD_NEXT, \"munmap\");\n> > > +}\n> > > +\n> > > +V4L2CompatManager::~V4L2CompatManager()\n> > > +{\n> > > +\tdevices_.clear();\n> > > +\tmmaps_.clear();\n> > > +\tproxies_.clear();\n> \n> Wouldn't it be better to clear proxies_ after stopping the thread ? The\n> vector stores unique pointers, clearing it will delete all proxies,\n> which in turn deletes the V4L2Camera instances that are accessible from\n> the thread.\n> \n> > > +\n> > > +\tif (isRunning()) {\n> > > +\t\texit(0);\n> > > +\t\t/* \\todo Wait with a timeout, just in case. */\n> > > +\t\twait();\n> > > +\t}\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::init()\n> > > +{\n> > > +\tstart();\n> > > +\n> > > +\tMutexLocker locker(mutex_);\n> > > +\tcv_.wait(locker, []{ return V4L2CompatManager::instance()->initialized_; });\n> \n> We're in an instance method, so there's no need to go through\n> V4L2CompatManager::instance(), you can return initialized_; directly.\n> The compiler will then throw an error:\n> \n> ../../src/v4l2/v4l2_compat_manager.cpp: In lambda function:\n> ../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: ‘this’ was not captured for this lambda function\n>   cv_.wait(locker, []{ return initialized_; });\n>                               ^~~~~~~~~~~~\n> ../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: invalid use of non-static data member ‘V4L2CompatManager::initialized_’\n> In file included from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n> ../../src/v4l2/v4l2_compat_manager.h:79:7: note: declared here\n>   bool initialized_;\n>        ^~~~~~~~~~~~\n> In file included from ../../src/v4l2/v4l2_compat_manager.h:11,\n>                  from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n> /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable: In instantiation of ‘void std::condition_variable::wait(std::unique_lock<std::mutex>&, _Predicate) [with _Predicate = V4L2CompatManager::init()::<lambda()>]’:\n> ../../src/v4l2/v4l2_compat_manager.cpp:64:45:   required from here\n> /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:13: error: could not convert ‘__p.V4L2CompatManager::init()::<lambda()>()’ from ‘void’ to ‘bool’\n>   while (!__p())\n>           ~~~^~\n> /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:9: error: in argument to unary !\n>   while (!__p())\n>          ^~~~~~\n> \n> which I assume led you to using V4L2CompatManager::instance(). The right\n> fix is to capture 'this' for the lambda function. You can read mode\n> about lambda functions and captures at\n> https://en.cppreference.com/w/cpp/language/lambda.\n> \n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +void V4L2CompatManager::run()\n> > > +{\n> > > +\tcm_ = new CameraManager();\n> > > +\n> > > +\tint ret = cm_->start();\n> > > +\tif (ret) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Failed to start camera manager: \"\n> > > +\t\t\t\t       << strerror(-ret);\n> > > +\t\treturn;\n> > > +\t}\n> > > +\n> > > +\tLOG(V4L2Compat, Debug) << \"Started camera manager\";\n> > > +\n> > > +\t/*\n> > > +\t * For each Camera registered in the system, a V4L2CameraProxy gets\n> > > +\t * created here to wrap a camera device.\n> > > +\t */\n> > > +\tunsigned int index = 0;\n> > > +\tfor (auto &camera : cm_->cameras()) {\n> > > +\t\tV4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);\n> > > +\t\tproxies_.emplace_back(proxy);\n> > > +\t\t++index;\n> > > +\t}\n> > > +\n> > > +\t/*\n> > > +\t * libcamera has been initialized. Unlock the init() caller as we're\n> > > +\t * now ready to handle calls from the framework.\n> > > +\t */\n> > > +\tmutex_.lock();\n> > > +\tinitialized_ = true;\n> > > +\tmutex_.unlock();\n> > > +\tcv_.notify_one();\n> > > +\n> > > +\t/* Now start processing events and messages. */\n> > > +\texec();\n> > > +\n> > > +\tcm_->stop();\n> > > +\tdelete cm_;\n> > > +\tcm_ = nullptr;\n> > > +}\n> > > +\n> > > +V4L2CompatManager *V4L2CompatManager::instance()\n> > > +{\n> > > +\tstatic V4L2CompatManager instance;\n> > > +\treturn &instance;\n> > > +}\n> > > +\n> > > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> > \n> > or getProxy() ?\n> \n> Good idea.\n> \n> > > +{\n> > > +\tauto device = devices_.find(fd);\n> > > +\tif (device == devices_.end())\n> > > +\t\treturn nullptr;\n> > > +\n> > > +\treturn device->second;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::getCameraIndex(int fd)\n> > > +{\n> > > +\tstruct stat statbuf;\n> > > +\tfstat(fd, &statbuf);\n> > > +\tunsigned int devMajor = major(statbuf.st_rdev);\n> > > +\tunsigned int devMinor = minor(statbuf.st_rdev);\n> > > +\n> > > +\tstd::shared_ptr<Camera> target = cm_->get(makedev(devMajor, devMinor));\n> > \n> > Shouldn't you check for (target != nullptr)\n> \n> Agreed.\n> \n> > > +\n> > > +\tunsigned int index = 0;\n> > > +\tfor (auto &camera : cm_->cameras()) {\n> > > +\t\tif (camera == target)\n> > > +\t\t\tbreak;\n> > > +\t\t++index;\n> > > +\t}\n> > > +\n> > > +\tif (index >= cm_->cameras().size())\n> > > +\t\treturn -1;\n> > \n> > This is a bit convoluted...\n> > A V4l2CameraProxy has the index and Camera, so once you got the target\n> > Camera from the CameraManager you could use std::find_if<> on proxies_\n> > with a V4L2CameraProxy::match(Camera *) function.\n> \n> Is it worth the additional complexity though, given that both are O(n)\n> anyway (and with a small n) ? I think we'll have to revisit this to\n> handle hotplug in any case, so I'd keep it as-is.\n> \n> > > +\n> > > +\treturn index;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode)\n> > > +{\n> > > +\tint fd = openat_func_(dirfd, path, oflag, mode);\n> > > +\tif (fd < 0)\n> > > +\t\treturn fd;\n> > > +\n> > > +\tstruct stat statbuf;\n> > > +\tfstat(fd, &statbuf);\n> > > +\tif (major(statbuf.st_rdev) != 81)\n> \n> In the very unlikely case that statbuf.st_rdev is initialized to 81 due\n> to random stack data, an fstat() failure won't be caught here. I woult\n> thus test\n> \n> \tif (ret < 0 || major(statbuf.st_rdev) != 81)\n> \n> For completeness you should also test that the device is a character\n> device and not a block device, so\n> \n> \tif (ret < 0 || (statbuf.st_mode & S_IFMT) != S_IFCHR ||\n> \t    major(statbuf.st_rdev) != 81)\n> \n> > > +\t\treturn fd;\n> > > +\n> > > +\tif (!isRunning())\n> > > +\t\tinit();\n> > > +\n> > > +\tint ret = getCameraIndex(fd);\n> > > +\tif (ret < 0) {\n> > > +\t\tLOG(V4L2Compat, Info) << \"No camera found for \" << path;\n> > > +\t\treturn fd;\n> > \n> > fd > 0 here\n> > and fd should be closed before returning (above here too)\n> \n> I think this is actually correct, the device isn't handled by libcamera,\n> so we forward openat().\n> \n> > > +\t}\n> > > +\n> > > +\tclose_func_(fd);\n> > > +\n> > > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > > +\n> > > +\tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> > > +\tret = proxy->open(oflag & O_NONBLOCK);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tint efd = eventfd(0, (oflag & O_CLOEXEC) | (oflag & O_NONBLOCK));\n> \n> Do you prefer this over\n> \n> \tint efd = eventfd(0, oflag & (O_CLOEXEC | O_NONBLOCK));\n> \n> for a specific reason ? There's no need to change it if you prefer your\n> version, I'm just curious.\n> \n> > > +\tif (efd < 0)\n> > > +\t\treturn efd;\n> > > +\n> > > +\tdevices_.emplace(efd, proxy);\n> > > +\n> > > +\treturn efd;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::dup(int oldfd)\n> > > +{\n> > > +\tint newfd = dup_func_(oldfd);\n> > > +\tif (getCamera(oldfd)) {\n> > > +\t\tdevices_[oldfd]->dup();\n> > > +\t\tdevices_[newfd] = devices_[oldfd];\n> > > +\t}\n> \n> You would avoid a few lookups with\n> \n> \tauto device = devices_.find(oldfd);\n> \tif (device != devices_.end()) {\n> \t\tV4L2CameraProxy *proxy = device->second;\n> \t\tdevices_[newfd] = proxy;\n> \t\tproxy->dup();\n> \t}\n> \n> > > +\n> > > +\treturn newfd;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::close(int fd)\n> > > +{\n> > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > +\tif (proxy) {\n> > > +\t\tint ret = proxy->close();\n> > > +\t\tdevices_.erase(fd);\n> > > +\t\treturn ret;\n> > > +\t}\n> > > +\n> > > +\treturn close_func_(fd);\n> > > +}\n> > > +\n> > > +void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,\n> > > +\t\t\t      int fd, off_t offset)\n> > > +{\n> > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > +\tif (!proxy)\n> > > +\t\treturn mmap_func_(addr, length, prot, flags, fd, offset);\n> > > +\n> > > +\tvoid *map = proxy->mmap(addr, length, prot, flags, offset);\n> > > +\tif (map == MAP_FAILED)\n> > > +\t\treturn map;\n> > > +\n> > > +\tmmaps_[map] = proxy;\n> > > +\treturn map;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > > +{\n> > > +\tauto device = mmaps_.find(addr);\n> > > +\tif (device == mmaps_.end())\n> > > +\t\treturn munmap_func_(addr, length);\n> > > +\n> > > +\tV4L2CameraProxy *proxy = device->second;\n> > > +\n> > > +\tint ret = proxy->munmap(addr, length);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tmmaps_.erase(device);\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)\n> > > +{\n> > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > +\tif (!proxy)\n> > > +\t\treturn ioctl_func_(fd, request, arg);\n> > > +\n> > > +\treturn proxy->ioctl(request, arg);\n> > > +}\n> > > +\n> > > +/* \\todo make libcamera export these */\n> > > +int V4L2CompatManager::bplMultiplier(unsigned int format)\n> > > +{\n> > > +\tswitch (format) {\n> > > +\tcase V4L2_PIX_FMT_NV12:\n> > > +\tcase V4L2_PIX_FMT_NV21:\n> > > +\tcase V4L2_PIX_FMT_NV16:\n> > > +\tcase V4L2_PIX_FMT_NV61:\n> > > +\tcase V4L2_PIX_FMT_NV24:\n> > > +\tcase V4L2_PIX_FMT_NV42:\n> > > +\t\treturn 1;\n> > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > +\t\treturn 3;\n> > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > +\t\treturn 4;\n> > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > +\t\treturn 2;\n> > > +\tdefault:\n> > > +\t\treturn 0;\n> > > +\t};\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::imageSize(unsigned int format,\n> > > +\t\t\t\t unsigned int width, unsigned int height)\n> > > +{\n> > > +\tswitch (format) {\n> > > +\tcase V4L2_PIX_FMT_NV12:\n> > > +\tcase V4L2_PIX_FMT_NV21:\n> > > +\t\treturn width * height + width * height / 2;\n> > > +\tcase V4L2_PIX_FMT_NV16:\n> > > +\tcase V4L2_PIX_FMT_NV61:\n> > > +\t\treturn width * height * 2;\n> > > +\tcase V4L2_PIX_FMT_NV24:\n> > > +\tcase V4L2_PIX_FMT_NV42:\n> > > +\t\treturn width * height * 3;\n> > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > +\t\treturn width * height * 3;\n> > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > +\t\treturn width * height * 4;\n> > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > +\t\treturn width * height * 2;\n> > > +\tdefault:\n> > > +\t\treturn 0;\n> > > +\t};\n> > > +}\n> > > +\n> > > +unsigned int V4L2CompatManager::v4l2ToDrm(unsigned int pixelformat)\n> > > +{\n> > > +\tswitch (pixelformat) {\n> > > +\t/* RGB formats. */\n> > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > +\t\treturn DRM_FORMAT_BGR888;\n> > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > +\t\treturn DRM_FORMAT_RGB888;\n> > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > +\t\treturn DRM_FORMAT_BGRA8888;\n> > > +\n> > > +\t/* YUV packed formats. */\n> > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > +\t\treturn DRM_FORMAT_YUYV;\n> > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > +\t\treturn DRM_FORMAT_YVYU;\n> > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > +\t\treturn DRM_FORMAT_UYVY;\n> > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > +\t\treturn DRM_FORMAT_VYUY;\n> > > +\n> > > +\t/* YUY planar formats. */\n> > > +\tcase V4L2_PIX_FMT_NV16:\n> > > +\t\treturn DRM_FORMAT_NV16;\n> > > +\tcase V4L2_PIX_FMT_NV61:\n> > > +\t\treturn DRM_FORMAT_NV61;\n> > > +\tcase V4L2_PIX_FMT_NV12:\n> > > +\t\treturn DRM_FORMAT_NV12;\n> > > +\tcase V4L2_PIX_FMT_NV21:\n> > > +\t\treturn DRM_FORMAT_NV21;\n> > > +\tcase V4L2_PIX_FMT_NV24:\n> > > +\t\treturn DRM_FORMAT_NV24;\n> > > +\tcase V4L2_PIX_FMT_NV42:\n> > > +\t\treturn DRM_FORMAT_NV42;\n> > > +\tdefault:\n> > > +\t\treturn pixelformat;\n> > > +\t};\n> > > +}\n> > > +\n> > > +unsigned int V4L2CompatManager::drmToV4L2(unsigned int pixelformat)\n> > > +{\n> > > +\tswitch (pixelformat) {\n> > > +\t/* RGB formats. */\n> > > +\tcase DRM_FORMAT_BGR888:\n> > > +\t\treturn V4L2_PIX_FMT_RGB24;\n> > > +\tcase DRM_FORMAT_RGB888:\n> > > +\t\treturn V4L2_PIX_FMT_BGR24;\n> > > +\tcase DRM_FORMAT_BGRA8888:\n> > > +\t\treturn V4L2_PIX_FMT_ARGB32;\n> > > +\n> > > +\t/* YUV packed formats. */\n> > > +\tcase DRM_FORMAT_YUYV:\n> > > +\t\treturn V4L2_PIX_FMT_YUYV;\n> > > +\tcase DRM_FORMAT_YVYU:\n> > > +\t\treturn V4L2_PIX_FMT_YVYU;\n> > > +\tcase DRM_FORMAT_UYVY:\n> > > +\t\treturn V4L2_PIX_FMT_UYVY;\n> > > +\tcase DRM_FORMAT_VYUY:\n> > > +\t\treturn V4L2_PIX_FMT_VYUY;\n> > > +\n> > > +\t/* YUY planar formats. */\n> > > +\tcase DRM_FORMAT_NV16:\n> > > +\t\treturn V4L2_PIX_FMT_NV16;\n> > > +\tcase DRM_FORMAT_NV61:\n> > > +\t\treturn V4L2_PIX_FMT_NV61;\n> > > +\tcase DRM_FORMAT_NV12:\n> > > +\t\treturn V4L2_PIX_FMT_NV12;\n> > > +\tcase DRM_FORMAT_NV21:\n> > > +\t\treturn V4L2_PIX_FMT_NV21;\n> > > +\tcase DRM_FORMAT_NV24:\n> > > +\t\treturn V4L2_PIX_FMT_NV24;\n> > > +\tcase DRM_FORMAT_NV42:\n> > > +\t\treturn V4L2_PIX_FMT_NV42;\n> > > +\tdefault:\n> > > +\t\treturn pixelformat;\n> > > +\t}\n> > > +}\n> > > diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h\n> > > new file mode 100644\n> > > index 00000000..b8ae6efe\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_compat_manager.h\n> > > @@ -0,0 +1,86 @@\n> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > +/*\n> > > + * Copyright (C) 2019, Google Inc.\n> > > + *\n> > > + * v4l2_compat_manager.h - V4L2 compatibility manager\n> > > + */\n> > > +\n> > > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > > +#define __V4L2_COMPAT_MANAGER_H__\n> > > +\n> > > +#include <condition_variable>\n> > > +#include <fcntl.h>\n> > > +#include <map>\n> > > +#include <mutex>\n> > > +#include <sys/types.h>\n> > > +#include <vector>\n> > > +\n> > > +#include <libcamera/camera.h>\n> > > +#include <libcamera/camera_manager.h>\n> > > +#include <libcamera/stream.h>\n> > \n> > You can drop stream.h and camera.h\n> > \n> > > +\n> > > +#include \"thread.h\"\n> > > +#include \"v4l2_camera_proxy.h\"\n> > > +\n> > > +using namespace libcamera;\n> > > +\n> > > +class V4L2CompatManager : public Thread\n> > > +{\n> > > +public:\n> > > +\tstatic V4L2CompatManager *instance();\n> > > +\n> > > +\tint init();\n> > > +\n> > > +\tV4L2CameraProxy *getCamera(int fd);\n> > > +\tV4L2CameraProxy *getCamera(void *addr);\n> > > +\n> > > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > > +\n> > > +\tint dup(int oldfd);\n> > > +\tint close(int fd);\n> > > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > > +\t\t   int fd, off_t offset);\n> > > +\tint munmap(void *addr, size_t length);\n> > > +\tint ioctl(int fd, unsigned long request, void *arg);\n> > > +\n> > > +\tstatic int bplMultiplier(unsigned int format);\n> > > +\tstatic int imageSize(unsigned int format, unsigned int width,\n> > > +\t\t\t     unsigned int height);\n> > > +\n> > > +\tstatic unsigned int v4l2ToDrm(unsigned int pixelformat);\n> > > +\tstatic unsigned int drmToV4L2(unsigned int pixelformat);\n> \n> Those four methods are now used by the V4L2CameraProxy class only, you\n> can move them there and make them private.\n> \n> > > +\n> > > +private:\n> > > +\tV4L2CompatManager();\n> > > +\t~V4L2CompatManager();\n> > > +\n> > > +\tvoid run() override;\n> > > +\tint getCameraIndex(int fd);\n> > > +\n> > > +\ttypedef int (*openat_func_t)(int dirfd, const char *path, int oflag, ...);\n> > > +\ttypedef int (*dup_func_t)(int oldfd);\n> > > +\ttypedef int (*close_func_t)(int fd);\n> > > +\ttypedef int (*ioctl_func_t)(int fd, unsigned long request, ...);\n> > > +\ttypedef void *(*mmap_func_t)(void *addr, size_t length, int prot,\n> > > +\t\t\t\t     int flags, int fd, off_t offset);\n> > > +\ttypedef int (*munmap_func_t)(void *addr, size_t length);\n> > > +\n> > > +\topenat_func_t openat_func_;\n> > > +\tdup_func_t    dup_func_;\n> > > +\tclose_func_t  close_func_;\n> > > +\tioctl_func_t  ioctl_func_;\n> > > +\tmmap_func_t   mmap_func_;\n> > > +\tmunmap_func_t munmap_func_;\n> > > +\n> > > +\tCameraManager *cm_;\n> > > +\n> > > +\tstd::mutex mutex_;\n> > > +\tstd::condition_variable cv_;\n> > > +\tbool initialized_;\n> > > +\n> > > +\tstd::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;\n> > > +\tstd::map<int, V4L2CameraProxy *> devices_;\n> > > +\tstd::map<void *, V4L2CameraProxy *> mmaps_;\n> > > +};\n> > > +\n> > > +#endif /* __V4L2_COMPAT_MANAGER_H__ */\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart","headers":{"Return-Path":"<paul.elder@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8E41D60465\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 31 Dec 2019 06:40:48 +0100 (CET)","from localhost.localdomain (173-16-160-11.client.mchsi.com\n\t[173.16.160.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id B3143DF;\n\tTue, 31 Dec 2019 06:40:46 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1577770848;\n\tbh=62gDLjSTarurk2tuDI1+/AfsbYUUs31YfDVT4mPaplc=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=mAQskqQfB20st7zRRPog83XO+cjjOAc3PtO/7VfpNjK/xDDEcVsagHGMa9BA+Q2k6\n\tV2BGEe604dVtdGttcXSE5lweO+3cucNChrr8MM0WprKbdi5+Qc1Q0eT68obP/2ZHtz\n\tGq/uTVFPyblIqVWvVW0EFbTV1defmy/hrqSF5A9E=","Date":"Mon, 30 Dec 2019 23:40:41 -0600","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20191231054041.GA24428@localhost.localdomain>","References":"<20191223072620.13022-1-paul.elder@ideasonboard.com>\n\t<20191223072620.13022-6-paul.elder@ideasonboard.com>\n\t<20191227145031.5abrggqog3pqjj5k@uno.localdomain>\n\t<20191228004007.GC5747@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20191228004007.GC5747@pendragon.ideasonboard.com>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 31 Dec 2019 05:40:48 -0000"}},{"id":3290,"web_url":"https://patchwork.libcamera.org/comment/3290/","msgid":"<20191231121822.GA4888@pendragon.ideasonboard.com>","date":"2019-12-31T12:18:22","subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Paul,\n\nOn Mon, Dec 30, 2019 at 11:40:41PM -0600, Paul Elder wrote:\n> On Sat, Dec 28, 2019 at 02:40:07AM +0200, Laurent Pinchart wrote:\n> > On Fri, Dec 27, 2019 at 03:50:31PM +0100, Jacopo Mondi wrote:\n> > > On Mon, Dec 23, 2019 at 01:26:19AM -0600, Paul Elder wrote:\n> > > > Add libcamera V4L2 compatibility layer.\n> > > >\n> > > > This initial implementation supports the minimal set of V4L2 operations,\n> > > > which allows getting, setting, and enumerating formats, and streaming\n> > > > frames from a video device. Some data about the wrapped V4L2 video\n> > > > device are hardcoded.\n> > > >\n> > > > Add a build option named 'v4l2' and adjust the build system to\n> > > > selectively compile the V4L2 compatibility layer.\n> > > >\n> > > > For now we match the V4L2 device node to a libcamera camera based on a\n> > > > devnum that a device enumerator may optionally map to a libcamera\n> > \n> > Isn't it the pipeline handler that maps devnums to cameras, not the\n> > device enumerator ?\n> > \n> > > > camera.\n> > > >\n> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > > >\n> > > > ---\n> > > > Major changes in v3:\n> > > > - V4L2CompatManager::openat() verify video node and match to camera via\n> > > >   devnum instead of path\n> > > > - add FrameMetadata class to encapsulate portions of the libcamera\n> > > >   Buffer that needs to be extracted to v4l2_buffer, and to prepare for\n> > > >   the Buffer rework\n> > > > - V4L2CameraProxy refcount for tracking dups and closes (and open)\n> > > > - add V4L2CompatManager::initialized_ to deal with race of waiting on\n> > > >   init cv after the cv is notified\n> > > >\n> > > > Less major changes in v3:\n> > > > - change the list of pending Reqeusts (v4l2 buffers queued before\n> > > >   streamon) to unique pointers\n> > > > - V4L2Camera::updateSizeImage() -> V4L2CameraProxy::calculateSizeImage()\n> > > > - change V4L2CompatManager list of V4L2CameraProxy to unique_ptr, with\n> > > >   maps of fd/mmap addr to V4L2CameraProxy *\n> > > > - removed cross-thread validation methods from V4L2CameraProxy\n> > > >   (validate[Stream|Memory]Type)\n> > > > - removed hasPixelFormat() and hasSize() from V4L2CameraProxy\n> > > > - moved mmap logic out of V4L2Camera and into V4L2CameraProxy\n> > > > - moved nonblock logic out of V4L2Camera and into V4L2CameraProxy\n> > > > - add todos\n> > > > - oter cosmetic changes\n> > > >\n> > > > Changes in v2:\n> > > > - move all errno acrobatics to V4L2CameraProxy\n> > > > - remove all mentions of dmabuf\n> > > > - make V4L2CompatManager::getCamera return pointer rather than\n> > > >   shared_ptr\n> > > > - match V4L2 device nodes to libcamera cameras using Camera::name()\n> > > >   compared to /sys/dev/char/maj:min/name (only works for UVC cameras)\n> > > >   - in V4L2CompatManager::getCameraIndex()\n> > > > - add -fvisibility=hidden to v4l2 compat\n> > > > - cache the results of V4L2CompatManager::imageSize() within V4L2Camera\n> > > >   (where V4L2Camera is interested)\n> > > > - remove V4L2CompatManager::valid[fd|mmap], and where those methods were\n> > > >   used, check V4L2CompatManager::getCamera() != nullptr instead\n> > > > - fixed V4L2CompatManager::drmToV4L2() mappings for DRM_FORMAT_BGR888\n> > > >   and DRM_FORMAT_RGB888\n> > > > - other cosmetic changes\n> > > > ---\n> > > >  meson_options.txt                |   5 +\n> > > >  src/meson.build                  |   4 +\n> > > >  src/v4l2/meson.build             |  31 ++\n> > > >  src/v4l2/v4l2_camera.cpp         | 248 ++++++++++++++++\n> > > >  src/v4l2/v4l2_camera.h           |  84 ++++++\n> > > >  src/v4l2/v4l2_camera_proxy.cpp   | 480 +++++++++++++++++++++++++++++++\n> > > >  src/v4l2/v4l2_camera_proxy.h     |  70 +++++\n> > > >  src/v4l2/v4l2_compat.cpp         |  78 +++++\n> > > >  src/v4l2/v4l2_compat_manager.cpp | 379 ++++++++++++++++++++++++\n> > > >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> > > >  10 files changed, 1465 insertions(+)\n> > > >  create mode 100644 src/v4l2/meson.build\n> > > >  create mode 100644 src/v4l2/v4l2_camera.cpp\n> > > >  create mode 100644 src/v4l2/v4l2_camera.h\n> > > >  create mode 100644 src/v4l2/v4l2_camera_proxy.cpp\n> > > >  create mode 100644 src/v4l2/v4l2_camera_proxy.h\n> > > >  create mode 100644 src/v4l2/v4l2_compat.cpp\n> > > >  create mode 100644 src/v4l2/v4l2_compat_manager.cpp\n> > > >  create mode 100644 src/v4l2/v4l2_compat_manager.h\n> > > >\n> > > > diff --git a/meson_options.txt b/meson_options.txt\n> > > > index 1a328045..b06dd494 100644\n> > > > --- a/meson_options.txt\n> > > > +++ b/meson_options.txt\n> > > > @@ -10,3 +10,8 @@ option('documentation',\n> > > >  option('test',\n> > > >          type : 'boolean',\n> > > >          description: 'Compile and include the tests')\n> > > > +\n> > > > +option('v4l2',\n> > > > +        type : 'boolean',\n> > > > +        value : false,\n> > > > +        description : 'Compile libcamera with V4L2 compatibility layer')\n> > \n> > 'Compile the V4L2 compatibility layer'\n> > \n> > (I incorrectly told you to disregard my whole comment in v2, when I\n> > meant disregarding the s/compatibility/adaptation/)\n> > \n> > > > diff --git a/src/meson.build b/src/meson.build\n> > > > index 67ad20aa..5adcd61f 100644\n> > > > --- a/src/meson.build\n> > > > +++ b/src/meson.build\n> > > > @@ -6,3 +6,7 @@ subdir('libcamera')\n> > > >  subdir('ipa')\n> > > >  subdir('cam')\n> > > >  subdir('qcam')\n> > > > +\n> > > > +if get_option('v4l2')\n> > > > +    subdir('v4l2')\n> > > > +endif\n> > > > diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build\n> > > > new file mode 100644\n> > > > index 00000000..14ee3594\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/meson.build\n> > > > @@ -0,0 +1,31 @@\n> > > > +v4l2_compat_sources = files([\n> > > > +    'v4l2_camera.cpp',\n> > > > +    'v4l2_camera_proxy.cpp',\n> > > > +    'v4l2_compat.cpp',\n> > > > +    'v4l2_compat_manager.cpp',\n> > > > +])\n> > > > +\n> > > > +v4l2_compat_includes = [\n> > > > +    libcamera_includes,\n> > > > +    libcamera_internal_includes,\n> > > > +]\n> > > > +\n> > > > +v4l2_compat_cpp_args = [\n> > > > +    # Meson enables large file support unconditionally, which redirect file\n> > > > +    # operations to 64-bit versions. This results in some symbols being\n> > > > +    # renamed, for instance open() being renamed to open64(). As the V4L2\n> > > > +    # adaptation wrapper needs to provide both 32-bit and 64-bit versions of\n> > > > +    # file operations, disable transparent large file support.\n> > > > +    '-U_FILE_OFFSET_BITS',\n> > > > +    '-D_FILE_OFFSET_BITS=32',\n> > > > +    '-fvisibility=hidden',\n> > > > +]\n> > > > +\n> > > > +v4l2_compat = shared_library('v4l2-compat',\n> > > > +                             v4l2_compat_sources,\n> > > > +                             name_prefix : '',\n> > > > +                             install : true,\n> > > > +                             link_with : libcamera,\n> > > > +                             include_directories : v4l2_compat_includes,\n> > > > +                             dependencies : libcamera_deps,\n> > > > +                             cpp_args : v4l2_compat_cpp_args)\n> > > > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > > > new file mode 100644\n> > > > index 00000000..2d33be9f\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_camera.cpp\n> > > > @@ -0,0 +1,248 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_camera.cpp - V4L2 compatibility camera\n> > > > + */\n> > > > +\n> > > > +#include \"v4l2_camera.h\"\n> > > > +\n> > > > +#include <errno.h>\n> > > > +#include <linux/videodev2.h>\n> > > > +#include <sys/mman.h>\n> > > > +#include <sys/syscall.h>\n> > > > +#include <time.h>\n> > > > +#include <unistd.h>\n> > > > +\n> > > > +#include \"log.h\"\n> > > > +#include \"utils.h\"\n> > > > +#include \"v4l2_compat_manager.h\"\n> > > \n> > > I think you can now drop this.\n> > > \n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > > > +\n> > > > +FrameMetadata::FrameMetadata(Buffer *buffer)\n> > > > +\t: index_(buffer->index()),\n> > > > +\t  bytesused_(buffer->bytesused()),\n> > > > +\t  timestamp_(buffer->timestamp()),\n> > > > +\t  sequence_(buffer->sequence()),\n> > > > +\t  status_(buffer->status())\n> > > \n> > > please span 80cols\n> > > \n> > > > +{\n> > > > +}\n> > > > +\n> > > > +V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)\n> > > > +\t: camera_(camera), bufferCount_(0), isRunning_(false)\n> > > > +{\n> > > > +\tcamera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);\n> > > > +};\n> > > \n> > > s/};/}\n> > > \n> > > > +\n> > > > +V4L2Camera::~V4L2Camera()\n> > > > +{\n> > > > +\tcamera_->release();\n> > > \n> > > Closing then destroying a V4L2Camera would cause a double release() ?\n> > \n> > It can, but I think that's fine, Camera::release() is a no-op in that\n> > case.\n> > \n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::open(int *ret)\n> > > > +{\n> > > > +\tif (camera_->acquire() < 0) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Failed to acquire camera\";\n> > > > +\t\t*ret = -EINVAL;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\tconfig_ = camera_->generateConfiguration({ StreamRole::Viewfinder });\n> > > > +\tif (!config_) {\n> > > > +\t\tcamera_->release();\n> > > > +\t\t*ret = -EINVAL;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\t*ret = 0;\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::close(int *ret)\n> > > > +{\n> > > > +\t*ret = camera_->release();\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)\n> > > > +{\n> > > > +\t*streamConfig = config_->at(0);\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::requestComplete(Request *request)\n> > > > +{\n> > > > +\tif (request->status() == Request::RequestCancelled)\n> > > > +\t\treturn;\n> > > > +\n> > > > +\t/* We only have one stream at the moment. */\n> > > > +\tbufferLock_.lock();\n> > > > +\tBuffer *buffer = request->buffers().begin()->second;\n> > > > +\tstd::unique_ptr<FrameMetadata> fmd =\n> > \n> > s/fmd/metadata/ ?\n> > \n> > > > +\t\tutils::make_unique<FrameMetadata>(buffer);\n> > > > +\tcompletedBuffers_.push(std::move(fmd));\n> > > > +\tbufferLock_.unlock();\n> > > > +\n> > > > +\tbufferSema_.release();\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::configure(int *ret, StreamConfiguration *streamConfigOut,\n> > > > +\t\t\t   Size *size, PixelFormat pixelformat,\n> > > > +\t\t\t   unsigned int bufferCount)\n> > > > +{\n> > > > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > > > +\tstreamConfig.size.width = size->width;\n> > > > +\tstreamConfig.size.height = size->height;\n> > > > +\tstreamConfig.pixelFormat = pixelformat;\n> > > > +\tbufferCount_ = bufferCount;\n> > > > +\tstreamConfig.bufferCount = bufferCount_;\n> > > > +\t/* \\todo memoryType (interval vs external) */\n> > > > +\n> > > > +\tCameraConfiguration::Status validation = config_->validate();\n> > > > +\tif (validation == CameraConfiguration::Invalid) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Configuration invalid\";\n> > > > +\t\t*ret = -EINVAL;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\tif (validation == CameraConfiguration::Adjusted)\n> > > > +\t\tLOG(V4L2Compat, Info) << \"Configuration adjusted\";\n> > > > +\n> > > > +\tLOG(V4L2Compat, Info) << \"Validated configuration is: \"\n> > > > +\t\t\t      << streamConfig.toString();\n> > > > +\n> > > > +\t*ret = camera_->configure(config_.get());\n> > > > +\tif (*ret < 0)\n> > > > +\t\treturn;\n> > > > +\n> > > > +\tbufferCount_ = streamConfig.bufferCount;\n> > > > +\n> > > > +\t*streamConfigOut = config_->at(0);\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::mmap(void **ret, unsigned int index)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> > > > +\n> > > > +\tStream *stream = *camera_->streams().begin();\n> > > > +\t*ret = stream->buffers()[index].planes()[0].mem();\n> > > \n> > > Should we check if index is not out of buffers() array bounds ?\n> > \n> > This is already checked by the caller.\n> > \n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> > > > +\n> > > > +\t*ret = camera_->allocateBuffers();\n> > > > +\tif (*ret == -EACCES)\n> > > > +\t\t*ret = -EBUSY;\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::freeBuffers()\n> > > > +{\n> > > > +\tcamera_->freeBuffers();\n> > > > +\tbufferCount_ = 0;\n> > > \n> > > is bufferCount_ used at all ?\n> > \n> > Not anymore in this class, it's probably a leftover.\n> > \n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::streamOn(int *ret)\n> > > > +{\n> > > > +\t*ret = 0;\n> > > > +\n> > > > +\tif (isRunning_)\n> > > > +\t\treturn;\n> > > > +\n> > > > +\t*ret = camera_->start();\n> > > > +\tif (*ret < 0) {\n> > > > +\t\tif (*ret == -EACCES)\n> > > > +\t\t\t*ret = -EBUSY;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\tisRunning_ = true;\n> > > > +\n> > > > +\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > > > +\t\t/* \\todo What should we do if this returns -EINVAL? */\n> > \n> > Good point, we'll have to implement better error handling here. Thanks\n> > for capturing it.\n> > \n> > > > +\t\t*ret = camera_->queueRequest(req.release());\n> > > > +\t\tif (*ret < 0) {\n> > > > +\t\t\t*ret = (*ret == -EACCES ? -EAGAIN : *ret);\n> > > > +\t\t\treturn;\n> > > > +\t\t}\n> > > > +\t}\n> > > > +\n> > > > +\tpendingRequests_.clear();\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::streamOff(int *ret)\n> > > > +{\n> > > > +\t*ret = 0;\n> > > > +\n> > > > +\t/* \\todo restore buffers to reqbufs state? */\n> > > > +\tif (!isRunning_)\n> > > > +\t\treturn;\n> > > > +\n> > > > +\t*ret = camera_->stop();\n> > > > +\tif (*ret < 0) {\n> > > > +\t\tif (*ret == -EACCES)\n> > > > +\t\t\t*ret = -EBUSY;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\tisRunning_ = false;\n> > > > +}\n> > > > +\n> > > > +void V4L2Camera::qbuf(int *ret, unsigned int index)\n> > > > +{\n> > > > +\tStream *stream = config_->at(0).stream();\n> > > > +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(index);\n> > > > +\tif (!buffer) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Can't create buffer\";\n> > > > +\t\t*ret = -ENOMEM;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\tstd::unique_ptr<Request> request =\n> > > > +\t\tstd::unique_ptr<Request>(camera_->createRequest());\n> > > > +\tif (!request) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > > > +\t\t*ret = -ENOMEM;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\t*ret = request->addBuffer(std::move(buffer));\n> > > > +\tif (*ret < 0) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Can't set buffer for request\";\n> > > > +\t\t*ret = -ENOMEM;\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\tif (!isRunning_) {\n> > > > +\t\tpendingRequests_.push_back(std::move(request));\n> > > > +\t} else {\n> > > > +\t\t*ret = camera_->queueRequest(request.release());\n> > > > +\t\tif (*ret < 0) {\n> > > > +\t\t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> > > > +\t\t\tif (*ret == -EACCES)\n> > > > +\t\t\t\t*ret = -EBUSY;\n> > > > +\t\t\treturn;\n> > > > +\t\t}\n> > > > +\t}\n> > > > +\n> > > > +\t*ret = 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2Camera::dqbuf(struct v4l2_buffer *arg, bool nonblock)\n> > > > +{\n> > > > +\tif (nonblock && !bufferSema_.tryAcquire())\n> > > > +\t\treturn -EAGAIN;\n> > > > +\telse\n> > > > +\t\tbufferSema_.acquire();\n> > > > +\n> > > > +\tbufferLock_.lock();\n> > > > +\tFrameMetadata *fmd = completedBuffers_.front().get();\n> > > > +\tcompletedBuffers_.pop();\n> > > > +\tbufferLock_.unlock();\n> > > > +\n> > > > +\targ->bytesused = fmd->bytesused();\n> > > > +\targ->field = V4L2_FIELD_NONE;\n> > > > +\targ->timestamp.tv_sec = fmd->timestamp() / 1000000000;\n> > > > +\targ->timestamp.tv_usec = fmd->timestamp() % 1000000;\n> > > > +\targ->sequence = fmd->sequence();\n> > > \n> > > What about the buffer index ?\n> > \n> > It's set by the caller.\n> > \n> > I'll review the qbuf/dqbuf strategy on patch 6/6.\n> > \n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > > > new file mode 100644\n> > > > index 00000000..13418b6b\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_camera.h\n> > > > @@ -0,0 +1,84 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_camera.h - V4L2 compatibility camera\n> > > > + */\n> > > > +\n> > > > +#ifndef __V4L2_CAMERA_H__\n> > > > +#define __V4L2_CAMERA_H__\n> > > > +\n> > > > +#include <deque>\n> > > > +#include <linux/videodev2.h>\n> > > > +#include <mutex>\n> > > > +#include <queue>\n> > > > +\n> > > > +#include <libcamera/buffer.h>\n> > > > +#include <libcamera/camera.h>\n> > > > +#include \"semaphore.h\"\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +class FrameMetadata\n> > > > +{\n> > > > +public:\n> > > > +\tFrameMetadata(Buffer *buffer);\n> > > > +\n> > > > +\tint index() const { return index_; }\n> > > > +\n> > > > +\tunsigned int bytesused() const { return bytesused_; }\n> > > > +\tuint64_t timestamp() const { return timestamp_; }\n> > > > +\tunsigned int sequence() const { return sequence_; }\n> > > > +\n> > > > +\tBuffer::Status status() const { return status_; }\n> > > > +\n> > > > +private:\n> > > > +\tint index_;\n> > > > +\n> > > > +\tunsigned int bytesused_;\n> > > > +\tuint64_t timestamp_;\n> > > > +\tunsigned int sequence_;\n> > > > +\n> > > > +\tBuffer::Status status_;\n> > > > +};\n> > > > +\n> > > > +class V4L2Camera : public Object\n> > > > +{\n> > > > +public:\n> > > > +\tV4L2Camera(std::shared_ptr<Camera> camera);\n> > > > +\t~V4L2Camera();\n> > > > +\n> > > > +\tvoid open(int *ret);\n> > > > +\tvoid close(int *ret);\n> > > > +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> > > > +\n> > > > +\tvoid mmap(void **ret, unsigned int index);\n> > > > +\n> > > > +\tvoid configure(int *ret, StreamConfiguration *streamConfigOut, Size *size,\n> > > > +\t\t       PixelFormat pixelformat, unsigned int bufferCount);\n> > > > +\n> > > > +\tvoid allocBuffers(int *ret, unsigned int count);\n> > > > +\tvoid freeBuffers();\n> > > > +\tvoid streamOn(int *ret);\n> > > > +\tvoid streamOff(int *ret);\n> > > > +\n> > > > +\tvoid qbuf(int *ret, unsigned int index);\n> > > > +\tint dqbuf(struct v4l2_buffer *arg, bool nonblock);\n> > > > +\n> > > > +private:\n> > > > +\tvoid requestComplete(Request *request);\n> > > > +\n> > > > +\tstd::shared_ptr<Camera> camera_;\n> > > > +\tstd::unique_ptr<CameraConfiguration> config_;\n> > > > +\n> > > > +\tunsigned int bufferCount_;\n> > > > +\tbool isRunning_;\n> > > > +\n> > > > +\tSemaphore bufferSema_;\n> > > > +\tstd::mutex bufferLock_;\n> > > > +\n> > > > +\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > > > +\tstd::queue<std::unique_ptr<FrameMetadata>> completedBuffers_;\n> > > > +};\n> > > > +\n> > > > +#endif /* __V4L2_CAMERA_H__ */\n> > > > diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp\n> > > > new file mode 100644\n> > > > index 00000000..b0acd477\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> > > > @@ -0,0 +1,480 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera\n> > > > + */\n> > > > +\n> > > > +#include \"v4l2_camera_proxy.h\"\n> > > > +\n> > > > +#include <algorithm>\n> > > > +#include <linux/videodev2.h>\n> > > > +#include <string.h>\n> > > > +#include <sys/mman.h>\n> > > > +\n> > > > +#include <libcamera/camera.h>\n> > > > +#include <libcamera/object.h>\n> > > > +\n> > > > +#include \"log.h\"\n> > > > +#include \"utils.h\"\n> > > > +#include \"v4l2_camera.h\"\n> > > > +#include \"v4l2_compat_manager.h\"\n> > > > +\n> > > > +#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +LOG_DECLARE_CATEGORY(V4L2Compat);\n> > > > +\n> > > > +V4L2CameraProxy::V4L2CameraProxy(unsigned int index,\n> > > > +\t\t\t\t std::shared_ptr<Camera> camera)\n> > > > +\t: index_(index), bufferCount_(0), currentBuf_(0),\n> > > > +\t  vcam_(utils::make_unique<V4L2Camera>(camera))\n> > \n> > You need to initialize refcount_ to 0.\n> > \n> > > > +{\n> > > > +\tquerycap(camera);\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::open(bool nonBlocking)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing open\";\n> > > > +\n> > > > +\tint ret;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> > > > +\t\t\t    &ret);\n> > > > +\tif (ret < 0) {\n> > > > +\t\terrno = -ret;\n> > > > +\t\treturn -1;\n> > > > +\t}\n> > > > +\n> > > > +\tnonBlocking_ = nonBlocking;\n> > > > +\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > > > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > > > +\tsetFmtFromConfig(streamConfig_);\n> > > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > > \n> > > sizeimage_ could be 0, which means the default format is not supported\n> > > by our compat layer. Can this cause troubles later?\n> > \n> > Potentially, but that should not happen as the compat layer should\n> > support all formats supported by libcamera. I wonder if we could\n> > somehow enforce this at compilation time. Moving the DRM <-> V4L2\n> > format conversion functions to the libcamera core could help in this\n> > area. For now I think we can ignore the issue.\n> > \n> > > > +\n> > > > +\trefcount_++;\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +void V4L2CameraProxy::dup()\n> > > > +{\n> > > > +\trefcount_++;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::close()\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing close\";\n> > > > +\n> > > > +\tif (refcount_ > 1) {\n> > > > +\t\trefcount_--;\n> > \n> > You need to decrement refcount_ even if == 1. I would thus write\n> > \n> > \tif (--refcount_ > 0)\n> > \t\treturn 0;\n> > \n> > > > +\t\treturn 0;\n> > > > +\t}\n> > > \n> > > Should this be locked ?\n> > \n> > Eventually, but it's not the only thing that needs to be locked. It just\n> > occurred to me that we are very thread-unsafe if the application using\n> > the compat layer is threaded. That's OK, one step at a time :-) Let's\n> > just remember about it.\n> > \n> > > > +\n> > > > +\tint ret;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::close, ConnectionTypeBlocking, &ret);\n> > > > +\tif (ret < 0) {\n> > > > +\t\terrno = EIO;\n> > > > +\t\treturn -1;\n> > > > +\t}\n> > \n> > This ends up calling Camera::release() and forwarding the error up, and\n> > the only possible cause of error is an invalid state of the camera,\n> > which would result from a bug in the V4L2 adaptation layer. Let's\n> > simplify the code (saving us from handling refcount_ specially in case\n> > of close() errors) and avoid dealing with close() errors by making this\n> > method and V4L2Camera::close() void.\n> > \n> > > > +\n> > > > +\treturn ret;\n> > > > +}\n> > > > +\n> > > > +void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,\n> > > > +\t\t\t    off_t offset)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing mmap\";\n> > > > +\n> > > > +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> > > > +\t\terrno = ENOTSUP;\n> > > > +\t\treturn MAP_FAILED;\n> > > > +\t}\n> > > > +\n> > > > +\tunsigned int index = offset / sizeimage_;\n> > > > +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> > > > +\t\terrno = EINVAL;\n> > > > +\t\treturn MAP_FAILED;\n> > > > +\t}\n> > > > +\n> > > > +\tvoid *val;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> > > > +\t\t\t    &val, index);\n> > > > +\treturn val;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing munmap\";\n> > > > +\n> > > > +\tif (length != sizeimage_) {\n> > > > +\t\terrno = EINVAL;\n> > > > +\t\treturn -1;\n> > > > +\t}\n> > \n> > We should probably also keep a list of mapped addresses and check that\n> > addr is in that list. This can be left for later, but please add a \\todo\n> > item in that case.\n> > \n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +/* \\todo getDeviceCaps? getMemoryCaps? */\n> > \n> > What is this ?\n> > \n> > > > +\n> > > > +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> > > > +{\n> > > > +\treturn (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> > \n> > No need for parentheses.\n> > \n> > > > +}\n> > > > +\n> > > > +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> > > > +{\n> > > > +\treturn (memory == V4L2_MEMORY_MMAP);\n> > \n> > Same here.\n> > \n> > > > +}\n> > > > +\n> > > > +void V4L2CameraProxy::setFmtFromConfig(StreamConfiguration &streamConfig)\n> > > > +{\n> > > > +\tcurV4L2Format_.fmt.pix.width = streamConfig.size.width;\n> > > > +\tcurV4L2Format_.fmt.pix.height = streamConfig.size.height;\n> > > > +\tcurV4L2Format_.fmt.pix.pixelformat =\n> > > > +\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat);\n> > > > +\tcurV4L2Format_.fmt.pix.field = V4L2_FIELD_NONE;\n> > > > +\tcurV4L2Format_.fmt.pix.bytesperline =\n> > > > +\t\tV4L2CompatManager::bplMultiplier(\n> > > > +\t\t\tcurV4L2Format_.fmt.pix.pixelformat) *\n> > > > +\t\tcurV4L2Format_.fmt.pix.width;\n> > > > +\tcurV4L2Format_.fmt.pix.sizeimage =\n> > > > +\t\tV4L2CompatManager::imageSize(curV4L2Format_.fmt.pix.pixelformat,\n> > > > +\t\t\t\t\t     curV4L2Format_.fmt.pix.width,\n> > > > +\t\t\t\t\t     curV4L2Format_.fmt.pix.height);\n> > > > +\tcurV4L2Format_.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;\n> > > > +}\n> > > > +\n> > > > +unsigned int V4L2CameraProxy::calculateSizeImage(StreamConfiguration &streamConfig)\n> > > > +{\n> > > > +\treturn V4L2CompatManager::imageSize(\n> > > > +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> > > > +\t\t\tstreamConfig.size.width,\n> > > > +\t\t\tstreamConfig.size.height);\n> > > > +}\n> > > > +\n> > > > +void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera)\n> > \n> > This is an instance method, no need to pass camera as an argument, you\n> > can use vcam_.\n> \n> I need access to camera for camera->name()... I could extract it in the\n> constructor and pass it in as an argument maybe? (too late, as I just\n> sent v4, though I prepared the below reply before)\n> \n> In any case, I can't get it from vcam_->camera_, since that's private,\n> and I was under the impression that we didn't want friend classes :/\n> \n> Also this entire method looked a bit bulky to put into the constructor.\n\nOK let's leave it as-is for now.\n\n> > > > +{\n> > > > +\tstd::string driver = \"libcamera\";\n> > > > +\tstd::string bus_info = driver + \":\" + std::to_string(index_);\n> > > > +\n> > > > +\tmemcpy(capabilities_.driver, driver.c_str(),\n> > > > +\t       sizeof(capabilities_.driver));\n> > > > +\tmemcpy(capabilities_.card, camera->name().c_str(),\n> > > > +\t       sizeof(capabilities_.card));\n> > > > +\tmemcpy(capabilities_.bus_info, bus_info.c_str(),\n> > > > +\t       sizeof(capabilities_.bus_info));\n> > > > +\t/* \\todo Put this is header/config somewhere. */\n> > \n> > s/is/in/\n> > \n> > > > +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> > > > +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> > > > +\tcapabilities_.capabilities =\n> > > > +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\n> > > \n> > > Nit:\n> > > \tcapabilities_.capabilities = capabilities_.device_caps |\n> > > \t\t\t\t     V4L2_CAP_DEVICE_CAPS;\n> > \n> >  \tcapabilities_.capabilities = capabilities_.device_caps\n> >  \t\t\t\t   | V4L2_CAP_DEVICE_CAPS;\n> > \n> > :-)\n> > \n> > > > +\tmemset(capabilities_.reserved, 0, sizeof(capabilities_.reserved));\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querycap\";\n> > > > +\n> > > > +\tmemcpy(arg, &capabilities_, sizeof(*arg));\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_enum_fmt\";\n> > > > +\n> > > > +\tif (!validateStreamType(arg->type) ||\n> > > > +\t    arg->index > streamConfig_.formats().pixelformats().size())\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\t/* \\todo Add map from format to description. */\n> > > > +\tmemcpy(arg->description, \"asdf\", 5);\n> > > \n> > > Could you at least provide a temporary name a little more meaningful ?\n> > \n> > Agreed.\n> > \n> > > > +\targ->pixelformat = V4L2CompatManager::drmToV4L2(\n> > > > +\t\t\t\tstreamConfig_.formats().pixelformats()[arg->index]);\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_g_fmt\";\n> > > > +\n> > > > +\tif (!validateStreamType(arg->type))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\targ->fmt.pix.width        = curV4L2Format_.fmt.pix.width;\n> > > > +\targ->fmt.pix.height       = curV4L2Format_.fmt.pix.height;\n> > > > +\targ->fmt.pix.pixelformat  = curV4L2Format_.fmt.pix.pixelformat;\n> > > > +\targ->fmt.pix.field        = curV4L2Format_.fmt.pix.field;\n> > > > +\targ->fmt.pix.bytesperline = curV4L2Format_.fmt.pix.bytesperline;\n> > > > +\targ->fmt.pix.sizeimage    = curV4L2Format_.fmt.pix.sizeimage;\n> > > > +\targ->fmt.pix.colorspace   = curV4L2Format_.fmt.pix.colorspace;\n> > > \n> > > Nit:\n> > > Couldn't you just assign arg->fmt.pix = curV4L2Format_.fmt.pix ?\n> > > It will make it easier to support multiplanar ?\n> > \n> > v4l2_pix_format doesn't contain pointers, so this should be sage and\n> > will simplify the code. Note that we should likely\n> > \n> > \tmemset(&arg->fmt, 0, sizeof(arg->fmt));\n> > \n> > before the assignment.\n> > \n> > It will be interesting to run v4l2-compliance on this :-)\n> > \n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_s_fmt\";\n> > > > +\n> > > > +\tint ret = vidioc_try_fmt(arg);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > > > +\t\t\t    &streamConfig_,\n> > > > +\t\t\t    new Size(arg->fmt.pix.width, arg->fmt.pix.height),\n> > \n> > You're leaking the newly allocate Size. Why don't you pass a const Size\n> > & instead of a Size * ? Were you trying to work around a compilation\n> > error ? :-)\n> > \n> > > > +\t\t\t    V4L2CompatManager::v4l2ToDrm(arg->fmt.pix.pixelformat),\n> > > > +\t\t\t    bufferCount_);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > > > +\tif (sizeimage_ == 0)\n> > > > +\t\treturn -EINVAL;\n> > \n> > A VIDIOC_S_FMT error shouldn't change the active configuration of the\n> > device. You thus need to work on a local copy of sizeimage_.\n> > \n> > > > +\n> > > > +\tsetFmtFromConfig(streamConfig_);\n> > \n> > You always call calculateSizeImage() and setFmtFromConfig() together,\n> > and calculateSizeImage() actually duplicates some code from\n> > setFmtFromConfig(). I would thus remove calculateSizeImage() and turn\n> > setFmtFromConfig() into an int function that will not perform any change\n> > to the active config if an error has to be returned. You could then\n> > remove sizeimage_ and replace it with curV4L2Format_.fmt.pix.sizeimage\n> > (with appropriate local variables if needed to keep the code readable).\n> \n> I've decided to keep them separate, because setFmtFromConfig does\n> initialization in V4L2CameraProxy::open(), where sizeimage (until we fix\n> v4l2 <-> drm conversions) may not be valid until we s_fmt/configure,\n> but we still want the curV4L2Format_ to be populated.\n\ncurV4L2Format_ contains a fmt.pix.sizeimage field which duplicates\nsizeimage_. I don't think we should have curV4L2Format_ only partly\nvalid. Furthermore I still don't see why we need two separate methods as\nthey're always called together with the same arguments.\n\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_try_fmt\";\n> > > > +\tif (!validateStreamType(arg->type))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tunsigned int format = arg->fmt.pix.pixelformat;\n> > > > +\tconst std::vector<PixelFormat> &formats =\n> > > > +\t\tstreamConfig_.formats().pixelformats();\n> > > > +\tif (std::find(formats.begin(), formats.end(), format) == formats.end())\n> > > > +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> > > > +\n> > > > +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> > > > +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> > > > +\tif (std::find(sizes.begin(), sizes.end(), size) == sizes.end())\n> > > > +\t\tsize = streamConfig_.formats().sizes(format)[0];\n> > > > +\n> > > > +\targ->fmt.pix.width        = size.width;\n> > > > +\targ->fmt.pix.height       = size.height;\n> > > > +\targ->fmt.pix.pixelformat  = format;\n> > > > +\targ->fmt.pix.field        = V4L2_FIELD_NONE;\n> > > > +\targ->fmt.pix.bytesperline =\n> > > > +\t\tV4L2CompatManager::bplMultiplier(\n> > > > +\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> > > > +\t\targ->fmt.pix.width;\n> > > > +\targ->fmt.pix.sizeimage    =\n> > > > +\t\tV4L2CompatManager::imageSize(\n> > > > +\t\t\tV4L2CompatManager::drmToV4L2(format),\n> > > > +\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n> > >\n> > > Nit:\n> > > \n> > > \targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n> > > \t\t\t\t\tV4L2CompatManager::drmToV4L2(format)) *\n> > > \t\t\t\t\targ->fmt.pix.width;\n> > > \targ->fmt.pix.sizeimage    = V4L2CompatManager::imageSize(\n> > > \t\t\t\t\tV4L2CompatManager::drmToV4L2(format),\n> > > \t\t\t\t\targ->fmt.pix.width, arg->fmt.pix.height);\n> > \n> > I'm not sure it's much easier to read. The good news is that moving\n> > bplMultiplier() and imageSize() to the V4L2CameraProxy class (as\n> > explained below) will help.\n> > \n> > > > +\targ->fmt.pix.colorspace   = V4L2_COLORSPACE_SRGB;\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)\n> > > > +{\n> > > > +\tint ret;\n> > > > +\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_reqbufs\";\n> > > > +\tif (!validateStreamType(arg->type) ||\n> > > > +\t    !validateMemoryType(arg->memory))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tLOG(V4L2Compat, Debug) << arg->count << \" bufs requested \";\n> > > > +\n> > > > +\targ->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;\n> > > > +\n> > > > +\tif (arg->count == 0) {\n> > > > +\t\tLOG(V4L2Compat, Debug) << \"Freeing libcamera bufs\";\n> > > > +\t\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > > > +\t\t\t\t    ConnectionTypeBlocking, &ret);\n> > > > +\t\tif (ret < 0) {\n> > > > +\t\t\tLOG(V4L2Compat, Error) << \"Failed to stop stream\";\n> > > > +\t\t\treturn ret;\n> > > > +\t\t}\n> > > > +\t\tvcam_->invokeMethod(&V4L2Camera::freeBuffers,\n> > > > +\t\t\t\t    ConnectionTypeBlocking);\n> > > > +\t\tbufferCount_ = 0;\n> > > > +\t\treturn 0;\n> > > > +\t}\n> > > > +\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::configure, ConnectionTypeBlocking, &ret,\n> > > > +\t\t\t    &streamConfig_,\n> > > > +\t\t\t    new Size(curV4L2Format_.fmt.pix.width,\n> > > > +\t\t\t\t     curV4L2Format_.fmt.pix.height),\n> > > > +\t\t\t    V4L2CompatManager::v4l2ToDrm(curV4L2Format_.fmt.pix.pixelformat),\n> > > > +\t\t\t    arg->count);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tsizeimage_ = calculateSizeImage(streamConfig_);\n> > > > +\tif (sizeimage_ == 0)\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tsetFmtFromConfig(streamConfig_);\n> > > \n> > > Do we need to go through configure() again ?\n> > \n> > I was going to ask the same :-) It is valid for a V4L2 application to\n> > open a device and call VIDIOC_REQBUFS, so we need to handle that.\n> > Calling configure() here is an option, but maybe setting an initial\n> > configuration at open() time would be better.\n> \n> We need configure() again here because configuring the number of buffers\n> in libcamera is done via configure(), while in V4L2 it is done via\n> reqbufs, separate from s_fmt. This is also why we have bufferCount_.\n> \n> > > > +\n> > > > +\targ->count = streamConfig_.bufferCount;\n> > > > +\tbufferCount_ = arg->count;\n> > > > +\n> > > > +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> > > > +\t\treturn -EINVAL;\n> > > \n> > > I would fail earlier in this case.\n> > > Ah wait, you do already, don't you?\n> > \n> > Good catch :-)\n> > \n> > > > +\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::allocBuffers,\n> > > > +\t\t\t    ConnectionTypeBlocking, &ret, arg->count);\n> > > > +\tif (ret < 0) {\n> > > > +\t\targ->count = 0;\n> > > > +\t\treturn ret == -EACCES ? -EBUSY : ret;\n> > > > +\t}\n> > > > +\n> > > > +\tLOG(V4L2Compat, Debug) << \"Allocated \" << arg->count << \" buffers\";\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_querybuf\";\n> > > > +\tStream *stream = streamConfig_.stream();\n> > > > +\n> > > > +\tif (!validateStreamType(arg->type) ||\n> > > > +\t    arg->index >= stream->buffers().size())\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tunsigned int index = arg->index;\n> > > > +\tmemset(arg, 0, sizeof(*arg));\n> > > > +\targ->index = index;\n> > > > +\targ->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;\n> > > > +\targ->length = curV4L2Format_.fmt.pix.sizeimage;\n> > > > +\targ->memory = V4L2_MEMORY_MMAP;\n> > > > +\targ->m.offset = arg->index * curV4L2Format_.fmt.pix.sizeimage;\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_qbuf, index = \"\n> > > > +\t\t\t       << arg->index;\n> > > > +\n> > > > +\tStream *stream = streamConfig_.stream();\n> > > > +\n> > > > +\tif (!validateStreamType(arg->type) ||\n> > > > +\t    !validateMemoryType(arg->memory) ||\n> > > > +\t    arg->index >= stream->buffers().size())\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tint ret;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::qbuf, ConnectionTypeBlocking,\n> > > > +\t\t\t    &ret, arg->index);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> > > > +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> > \n> > The V4L2_BUF_FLAG_MAPPED flag should be set at mmap() time and cleared\n> > an munmap() time. It shouldn't be touched here.\n> > \n> > > > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> > > > +\n> > > > +\treturn ret;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_dqbuf\";\n> > > > +\n> > > > +\tif (!validateStreamType(arg->type) ||\n> > > > +\t    !validateMemoryType(arg->memory))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\targ->index = currentBuf_;\n> > > > +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> > > > +\n> > > > +\tint ret = vcam_->dqbuf(arg, nonBlocking_);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> > > > +\targ->flags |= V4L2_BUF_FLAG_DONE;\n> > > > +\n> > > > +\targ->length = sizeimage_;\n> > > > +\n> > > > +\treturn ret;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_streamon(int *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamon\";\n> > > > +\n> > > > +\tif (!validateStreamType(*arg))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tint ret;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::streamOn,\n> > > > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > > > +\treturn ret;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::vidioc_streamoff(int *arg)\n> > > > +{\n> > > > +\tLOG(V4L2Compat, Debug) << \"Servicing vidioc_streamoff\";\n> > > > +\n> > > > +\tif (!validateStreamType(*arg))\n> > > > +\t\treturn -EINVAL;\n> > > > +\n> > > > +\tint ret;\n> > > > +\tvcam_->invokeMethod(&V4L2Camera::streamOff,\n> > > > +\t\t\t    ConnectionTypeBlocking, &ret);\n> > > > +\treturn ret;\n> > > > +}\n> > > > +\n> > > > +int V4L2CameraProxy::ioctl(unsigned long request, void *arg)\n> > > > +{\n> > > > +\tint ret;\n> > > > +\tswitch (request) {\n> > > > +\tcase VIDIOC_QUERYCAP:\n> > > > +\t\tret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_ENUM_FMT:\n> > > > +\t\tret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_G_FMT:\n> > > > +\t\tret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_S_FMT:\n> > > > +\t\tret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_TRY_FMT:\n> > > > +\t\tret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_REQBUFS:\n> > > > +\t\tret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_QUERYBUF:\n> > > > +\t\tret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_QBUF:\n> > > > +\t\tret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_DQBUF:\n> > > > +\t\tret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_STREAMON:\n> > > > +\t\tret = vidioc_streamon(static_cast<int *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tcase VIDIOC_STREAMOFF:\n> > > > +\t\tret = vidioc_streamoff(static_cast<int *>(arg));\n> > > > +\t\tbreak;\n> > > > +\tdefault:\n> > > > +\t\tret = -ENOTTY;\n> > > > +\t\tbreak;\n> > > > +\t}\n> > > > +\n> > > > +\tif (ret < 0) {\n> > > > +\t\terrno = -ret;\n> > > > +\t\treturn -1;\n> > > > +\t}\n> > > > +\n> > > > +\treturn ret;\n> > > > +}\n> > > > diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h\n> > > > new file mode 100644\n> > > > index 00000000..51fdbe19\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_camera_proxy.h\n> > > > @@ -0,0 +1,70 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera\n> > > > + */\n> > > > +\n> > > > +#ifndef __V4L2_CAMERA_PROXY_H__\n> > > > +#define __V4L2_CAMERA_PROXY_H__\n> > > > +\n> > > > +#include <linux/videodev2.h>\n> > > > +#include <memory>\n> > > > +#include <sys/types.h>\n> > > > +\n> > > > +#include <libcamera/camera.h>\n> > > > +\n> > > > +#include \"v4l2_camera.h\"\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +class V4L2CameraProxy\n> > > > +{\n> > > > +public:\n> > > > +\tV4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);\n> > > > +\n> > > > +\tint open(bool nonBlocking);\n> > > > +\tvoid dup();\n> > > > +\tint close();\n> > > > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > > > +\t\t   off_t offset);\n> > > > +\tint munmap(void *addr, size_t length);\n> > > > +\n> > > > +\tint ioctl(unsigned long request, void *arg);\n> > > > +\n> > > > +\tunsigned int refcount() { return refcount_; }\n> > \n> > This method is not used, you can drop it.\n> > \n> > > > +\n> > > > +private:\n> > > > +\tbool validateStreamType(uint32_t type);\n> > > > +\tbool validateMemoryType(uint32_t memory);\n> > > > +\tvoid setFmtFromConfig(StreamConfiguration &streamConfig);\n> > > > +\tunsigned int calculateSizeImage(StreamConfiguration &streamConfig);\n> > > > +\tvoid querycap(std::shared_ptr<Camera> camera);\n> > > > +\n> > > > +\tint vidioc_querycap(struct v4l2_capability *arg);\n> > > > +\tint vidioc_enum_fmt(struct v4l2_fmtdesc *arg);\n> > > > +\tint vidioc_g_fmt(struct v4l2_format *arg);\n> > > > +\tint vidioc_s_fmt(struct v4l2_format *arg);\n> > > > +\tint vidioc_try_fmt(struct v4l2_format *arg);\n> > > > +\tint vidioc_reqbufs(struct v4l2_requestbuffers *arg);\n> > > > +\tint vidioc_querybuf(struct v4l2_buffer *arg);\n> > > > +\tint vidioc_qbuf(struct v4l2_buffer *arg);\n> > > > +\tint vidioc_dqbuf(struct v4l2_buffer *arg);\n> > > > +\tint vidioc_streamon(int *arg);\n> > > > +\tint vidioc_streamoff(int *arg);\n> > > > +\n> > > > +\tunsigned int refcount_;\n> > > > +\tunsigned int index_;\n> > > > +\tbool nonBlocking_;\n> > > > +\n> > > > +\tstruct v4l2_format curV4L2Format_;\n> > > > +\tStreamConfiguration streamConfig_;\n> > > > +\tstruct v4l2_capability capabilities_;\n> > > > +\tunsigned int bufferCount_;\n> > > > +\tunsigned int currentBuf_;\n> > > > +\tunsigned int sizeimage_;\n> > > > +\n> > > > +\tstd::unique_ptr<V4L2Camera> vcam_;\n> > > > +};\n> > > > +\n> > > > +#endif /* __V4L2_CAMERA_PROXY_H__ */\n> > > > diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> > > > new file mode 100644\n> > > > index 00000000..a6a7aed2\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_compat.cpp\n> > > > @@ -0,0 +1,78 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_compat.cpp - V4L2 compatibility layer\n> > > > + */\n> > > > +\n> > > > +#include \"v4l2_compat_manager.h\"\n> > > > +\n> > > > +#include <errno.h>\n> > > > +#include <fcntl.h>\n> > > > +#include <stdarg.h>\n> > > > +#include <sys/mman.h>\n> > > > +#include <sys/stat.h>\n> > > > +#include <sys/types.h>\n> > > > +\n> > > > +#define LIBCAMERA_PUBLIC __attribute__((visibility(\"default\")))\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +#define extract_va_arg(type, arg, last)\t\\\n> > > > +{\t\t\t\t\t\\\n> > > > +\tva_list ap;\t\t\t\\\n> > > > +\tva_start(ap, last);\t\t\\\n> > > > +\targ = va_arg(ap, type);\t\t\\\n> > > > +\tva_end(ap);\t\t\t\\\n> > > > +}\n> > > > +\n> > > > +extern \"C\" {\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)\n> > > > +{\n> > > > +\tmode_t mode = 0;\n> > > > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > > > +\t\textract_va_arg(mode_t, mode, oflag);\n> > > > +\n> > > > +\treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag, mode);\n> > \n> > Could you wrap lines at a 80 columns boundary when feasible ?\n> > \n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)\n> > > > +{\n> > > > +\tmode_t mode = 0;\n> > > > +\tif (oflag & O_CREAT || oflag & O_TMPFILE)\n> > > > +\t\textract_va_arg(mode_t, mode, oflag);\n> > > > +\n> > > > +\treturn V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);\n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int dup(int oldfd)\n> > > > +{\n> > > > +\treturn V4L2CompatManager::instance()->dup(oldfd);\n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int close(int fd)\n> > > > +{\n> > > > +\treturn V4L2CompatManager::instance()->close(fd);\n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,\n> > > > +\t\t\t    int fd, off_t offset)\n> > > > +{\n> > > > +\treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> > \n> > Same comment here.\n> > \n> > It's annoying that clang-format doesn't implement a soft boundary and a\n> > hard boundary.\n> > \n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)\n> > > > +{\n> > > > +\treturn V4L2CompatManager::instance()->munmap(addr, length);\n> > > > +}\n> > > > +\n> > > > +LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)\n> > > > +{\n> > > > +\tvoid *arg;\n> > > > +\textract_va_arg(void *, arg, request);\n> > > > +\n> > > > +\treturn V4L2CompatManager::instance()->ioctl(fd, request, arg);\n> > > > +}\n> > > > +\n> > > > +}\n> > > > diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp\n> > > > new file mode 100644\n> > > > index 00000000..bfe76e82\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > > > @@ -0,0 +1,379 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_compat_manager.cpp - V4L2 compatibility manager\n> > > > + */\n> > > > +\n> > > > +#include \"v4l2_compat_manager.h\"\n> > > > +\n> > > > +#include <dlfcn.h>\n> > > > +#include <fcntl.h>\n> > > > +#include <fstream>\n> > > > +#include <linux/drm_fourcc.h>\n> > > > +#include <linux/videodev2.h>\n> > > > +#include <map>\n> > > > +#include <stdarg.h>\n> > > > +#include <string.h>\n> > > > +#include <sys/eventfd.h>\n> > > > +#include <sys/mman.h>\n> > > > +#include <sys/stat.h>\n> > > > +#include <sys/sysmacros.h>\n> > > > +#include <sys/types.h>\n> > > > +#include <unistd.h>\n> > > > +\n> > > > +#include <libcamera/camera.h>\n> > > > +#include <libcamera/camera_manager.h>\n> > > > +#include <libcamera/stream.h>\n> > > > +\n> > > > +#include \"log.h\"\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +LOG_DEFINE_CATEGORY(V4L2Compat)\n> > > > +\n> > > > +V4L2CompatManager::V4L2CompatManager()\n> > > > +\t: cm_(nullptr), initialized_(false)\n> > > > +{\n> > > > +\topenat_func_ = (openat_func_t)dlsym(RTLD_NEXT, \"openat\");\n> > > > +\tdup_func_    = (dup_func_t   )dlsym(RTLD_NEXT, \"dup\");\n> > > > +\tclose_func_  = (close_func_t )dlsym(RTLD_NEXT, \"close\");\n> > > > +\tioctl_func_  = (ioctl_func_t )dlsym(RTLD_NEXT, \"ioctl\");\n> > > > +\tmmap_func_   = (mmap_func_t  )dlsym(RTLD_NEXT, \"mmap\");\n> > > > +\tmunmap_func_ = (munmap_func_t)dlsym(RTLD_NEXT, \"munmap\");\n> > > > +}\n> > > > +\n> > > > +V4L2CompatManager::~V4L2CompatManager()\n> > > > +{\n> > > > +\tdevices_.clear();\n> > > > +\tmmaps_.clear();\n> > > > +\tproxies_.clear();\n> > \n> > Wouldn't it be better to clear proxies_ after stopping the thread ? The\n> > vector stores unique pointers, clearing it will delete all proxies,\n> > which in turn deletes the V4L2Camera instances that are accessible from\n> > the thread.\n> > \n> > > > +\n> > > > +\tif (isRunning()) {\n> > > > +\t\texit(0);\n> > > > +\t\t/* \\todo Wait with a timeout, just in case. */\n> > > > +\t\twait();\n> > > > +\t}\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::init()\n> > > > +{\n> > > > +\tstart();\n> > > > +\n> > > > +\tMutexLocker locker(mutex_);\n> > > > +\tcv_.wait(locker, []{ return V4L2CompatManager::instance()->initialized_; });\n> > \n> > We're in an instance method, so there's no need to go through\n> > V4L2CompatManager::instance(), you can return initialized_; directly.\n> > The compiler will then throw an error:\n> > \n> > ../../src/v4l2/v4l2_compat_manager.cpp: In lambda function:\n> > ../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: ‘this’ was not captured for this lambda function\n> >   cv_.wait(locker, []{ return initialized_; });\n> >                               ^~~~~~~~~~~~\n> > ../../src/v4l2/v4l2_compat_manager.cpp:64:30: error: invalid use of non-static data member ‘V4L2CompatManager::initialized_’\n> > In file included from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n> > ../../src/v4l2/v4l2_compat_manager.h:79:7: note: declared here\n> >   bool initialized_;\n> >        ^~~~~~~~~~~~\n> > In file included from ../../src/v4l2/v4l2_compat_manager.h:11,\n> >                  from ../../src/v4l2/v4l2_compat_manager.cpp:8:\n> > /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable: In instantiation of ‘void std::condition_variable::wait(std::unique_lock<std::mutex>&, _Predicate) [with _Predicate = V4L2CompatManager::init()::<lambda()>]’:\n> > ../../src/v4l2/v4l2_compat_manager.cpp:64:45:   required from here\n> > /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:13: error: could not convert ‘__p.V4L2CompatManager::init()::<lambda()>()’ from ‘void’ to ‘bool’\n> >   while (!__p())\n> >           ~~~^~\n> > /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/g++-v8/condition_variable:98:9: error: in argument to unary !\n> >   while (!__p())\n> >          ^~~~~~\n> > \n> > which I assume led you to using V4L2CompatManager::instance(). The right\n> > fix is to capture 'this' for the lambda function. You can read mode\n> > about lambda functions and captures at\n> > https://en.cppreference.com/w/cpp/language/lambda.\n> > \n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +void V4L2CompatManager::run()\n> > > > +{\n> > > > +\tcm_ = new CameraManager();\n> > > > +\n> > > > +\tint ret = cm_->start();\n> > > > +\tif (ret) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"Failed to start camera manager: \"\n> > > > +\t\t\t\t       << strerror(-ret);\n> > > > +\t\treturn;\n> > > > +\t}\n> > > > +\n> > > > +\tLOG(V4L2Compat, Debug) << \"Started camera manager\";\n> > > > +\n> > > > +\t/*\n> > > > +\t * For each Camera registered in the system, a V4L2CameraProxy gets\n> > > > +\t * created here to wrap a camera device.\n> > > > +\t */\n> > > > +\tunsigned int index = 0;\n> > > > +\tfor (auto &camera : cm_->cameras()) {\n> > > > +\t\tV4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);\n> > > > +\t\tproxies_.emplace_back(proxy);\n> > > > +\t\t++index;\n> > > > +\t}\n> > > > +\n> > > > +\t/*\n> > > > +\t * libcamera has been initialized. Unlock the init() caller as we're\n> > > > +\t * now ready to handle calls from the framework.\n> > > > +\t */\n> > > > +\tmutex_.lock();\n> > > > +\tinitialized_ = true;\n> > > > +\tmutex_.unlock();\n> > > > +\tcv_.notify_one();\n> > > > +\n> > > > +\t/* Now start processing events and messages. */\n> > > > +\texec();\n> > > > +\n> > > > +\tcm_->stop();\n> > > > +\tdelete cm_;\n> > > > +\tcm_ = nullptr;\n> > > > +}\n> > > > +\n> > > > +V4L2CompatManager *V4L2CompatManager::instance()\n> > > > +{\n> > > > +\tstatic V4L2CompatManager instance;\n> > > > +\treturn &instance;\n> > > > +}\n> > > > +\n> > > > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> > > \n> > > or getProxy() ?\n> > \n> > Good idea.\n> > \n> > > > +{\n> > > > +\tauto device = devices_.find(fd);\n> > > > +\tif (device == devices_.end())\n> > > > +\t\treturn nullptr;\n> > > > +\n> > > > +\treturn device->second;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::getCameraIndex(int fd)\n> > > > +{\n> > > > +\tstruct stat statbuf;\n> > > > +\tfstat(fd, &statbuf);\n> > > > +\tunsigned int devMajor = major(statbuf.st_rdev);\n> > > > +\tunsigned int devMinor = minor(statbuf.st_rdev);\n> > > > +\n> > > > +\tstd::shared_ptr<Camera> target = cm_->get(makedev(devMajor, devMinor));\n> > > \n> > > Shouldn't you check for (target != nullptr)\n> > \n> > Agreed.\n> > \n> > > > +\n> > > > +\tunsigned int index = 0;\n> > > > +\tfor (auto &camera : cm_->cameras()) {\n> > > > +\t\tif (camera == target)\n> > > > +\t\t\tbreak;\n> > > > +\t\t++index;\n> > > > +\t}\n> > > > +\n> > > > +\tif (index >= cm_->cameras().size())\n> > > > +\t\treturn -1;\n> > > \n> > > This is a bit convoluted...\n> > > A V4l2CameraProxy has the index and Camera, so once you got the target\n> > > Camera from the CameraManager you could use std::find_if<> on proxies_\n> > > with a V4L2CameraProxy::match(Camera *) function.\n> > \n> > Is it worth the additional complexity though, given that both are O(n)\n> > anyway (and with a small n) ? I think we'll have to revisit this to\n> > handle hotplug in any case, so I'd keep it as-is.\n> > \n> > > > +\n> > > > +\treturn index;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode)\n> > > > +{\n> > > > +\tint fd = openat_func_(dirfd, path, oflag, mode);\n> > > > +\tif (fd < 0)\n> > > > +\t\treturn fd;\n> > > > +\n> > > > +\tstruct stat statbuf;\n> > > > +\tfstat(fd, &statbuf);\n> > > > +\tif (major(statbuf.st_rdev) != 81)\n> > \n> > In the very unlikely case that statbuf.st_rdev is initialized to 81 due\n> > to random stack data, an fstat() failure won't be caught here. I woult\n> > thus test\n> > \n> > \tif (ret < 0 || major(statbuf.st_rdev) != 81)\n> > \n> > For completeness you should also test that the device is a character\n> > device and not a block device, so\n> > \n> > \tif (ret < 0 || (statbuf.st_mode & S_IFMT) != S_IFCHR ||\n> > \t    major(statbuf.st_rdev) != 81)\n> > \n> > > > +\t\treturn fd;\n> > > > +\n> > > > +\tif (!isRunning())\n> > > > +\t\tinit();\n> > > > +\n> > > > +\tint ret = getCameraIndex(fd);\n> > > > +\tif (ret < 0) {\n> > > > +\t\tLOG(V4L2Compat, Info) << \"No camera found for \" << path;\n> > > > +\t\treturn fd;\n> > > \n> > > fd > 0 here\n> > > and fd should be closed before returning (above here too)\n> > \n> > I think this is actually correct, the device isn't handled by libcamera,\n> > so we forward openat().\n> > \n> > > > +\t}\n> > > > +\n> > > > +\tclose_func_(fd);\n> > > > +\n> > > > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > > > +\n> > > > +\tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> > > > +\tret = proxy->open(oflag & O_NONBLOCK);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\tint efd = eventfd(0, (oflag & O_CLOEXEC) | (oflag & O_NONBLOCK));\n> > \n> > Do you prefer this over\n> > \n> > \tint efd = eventfd(0, oflag & (O_CLOEXEC | O_NONBLOCK));\n> > \n> > for a specific reason ? There's no need to change it if you prefer your\n> > version, I'm just curious.\n> > \n> > > > +\tif (efd < 0)\n> > > > +\t\treturn efd;\n> > > > +\n> > > > +\tdevices_.emplace(efd, proxy);\n> > > > +\n> > > > +\treturn efd;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::dup(int oldfd)\n> > > > +{\n> > > > +\tint newfd = dup_func_(oldfd);\n> > > > +\tif (getCamera(oldfd)) {\n> > > > +\t\tdevices_[oldfd]->dup();\n> > > > +\t\tdevices_[newfd] = devices_[oldfd];\n> > > > +\t}\n> > \n> > You would avoid a few lookups with\n> > \n> > \tauto device = devices_.find(oldfd);\n> > \tif (device != devices_.end()) {\n> > \t\tV4L2CameraProxy *proxy = device->second;\n> > \t\tdevices_[newfd] = proxy;\n> > \t\tproxy->dup();\n> > \t}\n> > \n> > > > +\n> > > > +\treturn newfd;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::close(int fd)\n> > > > +{\n> > > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > > +\tif (proxy) {\n> > > > +\t\tint ret = proxy->close();\n> > > > +\t\tdevices_.erase(fd);\n> > > > +\t\treturn ret;\n> > > > +\t}\n> > > > +\n> > > > +\treturn close_func_(fd);\n> > > > +}\n> > > > +\n> > > > +void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,\n> > > > +\t\t\t      int fd, off_t offset)\n> > > > +{\n> > > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > > +\tif (!proxy)\n> > > > +\t\treturn mmap_func_(addr, length, prot, flags, fd, offset);\n> > > > +\n> > > > +\tvoid *map = proxy->mmap(addr, length, prot, flags, offset);\n> > > > +\tif (map == MAP_FAILED)\n> > > > +\t\treturn map;\n> > > > +\n> > > > +\tmmaps_[map] = proxy;\n> > > > +\treturn map;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > > > +{\n> > > > +\tauto device = mmaps_.find(addr);\n> > > > +\tif (device == mmaps_.end())\n> > > > +\t\treturn munmap_func_(addr, length);\n> > > > +\n> > > > +\tV4L2CameraProxy *proxy = device->second;\n> > > > +\n> > > > +\tint ret = proxy->munmap(addr, length);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\tmmaps_.erase(device);\n> > > > +\n> > > > +\treturn 0;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)\n> > > > +{\n> > > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > > +\tif (!proxy)\n> > > > +\t\treturn ioctl_func_(fd, request, arg);\n> > > > +\n> > > > +\treturn proxy->ioctl(request, arg);\n> > > > +}\n> > > > +\n> > > > +/* \\todo make libcamera export these */\n> > > > +int V4L2CompatManager::bplMultiplier(unsigned int format)\n> > > > +{\n> > > > +\tswitch (format) {\n> > > > +\tcase V4L2_PIX_FMT_NV12:\n> > > > +\tcase V4L2_PIX_FMT_NV21:\n> > > > +\tcase V4L2_PIX_FMT_NV16:\n> > > > +\tcase V4L2_PIX_FMT_NV61:\n> > > > +\tcase V4L2_PIX_FMT_NV24:\n> > > > +\tcase V4L2_PIX_FMT_NV42:\n> > > > +\t\treturn 1;\n> > > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > > +\t\treturn 3;\n> > > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > > +\t\treturn 4;\n> > > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > > +\t\treturn 2;\n> > > > +\tdefault:\n> > > > +\t\treturn 0;\n> > > > +\t};\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::imageSize(unsigned int format,\n> > > > +\t\t\t\t unsigned int width, unsigned int height)\n> > > > +{\n> > > > +\tswitch (format) {\n> > > > +\tcase V4L2_PIX_FMT_NV12:\n> > > > +\tcase V4L2_PIX_FMT_NV21:\n> > > > +\t\treturn width * height + width * height / 2;\n> > > > +\tcase V4L2_PIX_FMT_NV16:\n> > > > +\tcase V4L2_PIX_FMT_NV61:\n> > > > +\t\treturn width * height * 2;\n> > > > +\tcase V4L2_PIX_FMT_NV24:\n> > > > +\tcase V4L2_PIX_FMT_NV42:\n> > > > +\t\treturn width * height * 3;\n> > > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > > +\t\treturn width * height * 3;\n> > > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > > +\t\treturn width * height * 4;\n> > > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > > +\t\treturn width * height * 2;\n> > > > +\tdefault:\n> > > > +\t\treturn 0;\n> > > > +\t};\n> > > > +}\n> > > > +\n> > > > +unsigned int V4L2CompatManager::v4l2ToDrm(unsigned int pixelformat)\n> > > > +{\n> > > > +\tswitch (pixelformat) {\n> > > > +\t/* RGB formats. */\n> > > > +\tcase V4L2_PIX_FMT_RGB24:\n> > > > +\t\treturn DRM_FORMAT_BGR888;\n> > > > +\tcase V4L2_PIX_FMT_BGR24:\n> > > > +\t\treturn DRM_FORMAT_RGB888;\n> > > > +\tcase V4L2_PIX_FMT_ARGB32:\n> > > > +\t\treturn DRM_FORMAT_BGRA8888;\n> > > > +\n> > > > +\t/* YUV packed formats. */\n> > > > +\tcase V4L2_PIX_FMT_YUYV:\n> > > > +\t\treturn DRM_FORMAT_YUYV;\n> > > > +\tcase V4L2_PIX_FMT_YVYU:\n> > > > +\t\treturn DRM_FORMAT_YVYU;\n> > > > +\tcase V4L2_PIX_FMT_UYVY:\n> > > > +\t\treturn DRM_FORMAT_UYVY;\n> > > > +\tcase V4L2_PIX_FMT_VYUY:\n> > > > +\t\treturn DRM_FORMAT_VYUY;\n> > > > +\n> > > > +\t/* YUY planar formats. */\n> > > > +\tcase V4L2_PIX_FMT_NV16:\n> > > > +\t\treturn DRM_FORMAT_NV16;\n> > > > +\tcase V4L2_PIX_FMT_NV61:\n> > > > +\t\treturn DRM_FORMAT_NV61;\n> > > > +\tcase V4L2_PIX_FMT_NV12:\n> > > > +\t\treturn DRM_FORMAT_NV12;\n> > > > +\tcase V4L2_PIX_FMT_NV21:\n> > > > +\t\treturn DRM_FORMAT_NV21;\n> > > > +\tcase V4L2_PIX_FMT_NV24:\n> > > > +\t\treturn DRM_FORMAT_NV24;\n> > > > +\tcase V4L2_PIX_FMT_NV42:\n> > > > +\t\treturn DRM_FORMAT_NV42;\n> > > > +\tdefault:\n> > > > +\t\treturn pixelformat;\n> > > > +\t};\n> > > > +}\n> > > > +\n> > > > +unsigned int V4L2CompatManager::drmToV4L2(unsigned int pixelformat)\n> > > > +{\n> > > > +\tswitch (pixelformat) {\n> > > > +\t/* RGB formats. */\n> > > > +\tcase DRM_FORMAT_BGR888:\n> > > > +\t\treturn V4L2_PIX_FMT_RGB24;\n> > > > +\tcase DRM_FORMAT_RGB888:\n> > > > +\t\treturn V4L2_PIX_FMT_BGR24;\n> > > > +\tcase DRM_FORMAT_BGRA8888:\n> > > > +\t\treturn V4L2_PIX_FMT_ARGB32;\n> > > > +\n> > > > +\t/* YUV packed formats. */\n> > > > +\tcase DRM_FORMAT_YUYV:\n> > > > +\t\treturn V4L2_PIX_FMT_YUYV;\n> > > > +\tcase DRM_FORMAT_YVYU:\n> > > > +\t\treturn V4L2_PIX_FMT_YVYU;\n> > > > +\tcase DRM_FORMAT_UYVY:\n> > > > +\t\treturn V4L2_PIX_FMT_UYVY;\n> > > > +\tcase DRM_FORMAT_VYUY:\n> > > > +\t\treturn V4L2_PIX_FMT_VYUY;\n> > > > +\n> > > > +\t/* YUY planar formats. */\n> > > > +\tcase DRM_FORMAT_NV16:\n> > > > +\t\treturn V4L2_PIX_FMT_NV16;\n> > > > +\tcase DRM_FORMAT_NV61:\n> > > > +\t\treturn V4L2_PIX_FMT_NV61;\n> > > > +\tcase DRM_FORMAT_NV12:\n> > > > +\t\treturn V4L2_PIX_FMT_NV12;\n> > > > +\tcase DRM_FORMAT_NV21:\n> > > > +\t\treturn V4L2_PIX_FMT_NV21;\n> > > > +\tcase DRM_FORMAT_NV24:\n> > > > +\t\treturn V4L2_PIX_FMT_NV24;\n> > > > +\tcase DRM_FORMAT_NV42:\n> > > > +\t\treturn V4L2_PIX_FMT_NV42;\n> > > > +\tdefault:\n> > > > +\t\treturn pixelformat;\n> > > > +\t}\n> > > > +}\n> > > > diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h\n> > > > new file mode 100644\n> > > > index 00000000..b8ae6efe\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_compat_manager.h\n> > > > @@ -0,0 +1,86 @@\n> > > > +/* SPDX-License-Identifier: GPL-2.0-or-later */\n> > > > +/*\n> > > > + * Copyright (C) 2019, Google Inc.\n> > > > + *\n> > > > + * v4l2_compat_manager.h - V4L2 compatibility manager\n> > > > + */\n> > > > +\n> > > > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > > > +#define __V4L2_COMPAT_MANAGER_H__\n> > > > +\n> > > > +#include <condition_variable>\n> > > > +#include <fcntl.h>\n> > > > +#include <map>\n> > > > +#include <mutex>\n> > > > +#include <sys/types.h>\n> > > > +#include <vector>\n> > > > +\n> > > > +#include <libcamera/camera.h>\n> > > > +#include <libcamera/camera_manager.h>\n> > > > +#include <libcamera/stream.h>\n> > > \n> > > You can drop stream.h and camera.h\n> > > \n> > > > +\n> > > > +#include \"thread.h\"\n> > > > +#include \"v4l2_camera_proxy.h\"\n> > > > +\n> > > > +using namespace libcamera;\n> > > > +\n> > > > +class V4L2CompatManager : public Thread\n> > > > +{\n> > > > +public:\n> > > > +\tstatic V4L2CompatManager *instance();\n> > > > +\n> > > > +\tint init();\n> > > > +\n> > > > +\tV4L2CameraProxy *getCamera(int fd);\n> > > > +\tV4L2CameraProxy *getCamera(void *addr);\n> > > > +\n> > > > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > > > +\n> > > > +\tint dup(int oldfd);\n> > > > +\tint close(int fd);\n> > > > +\tvoid *mmap(void *addr, size_t length, int prot, int flags,\n> > > > +\t\t   int fd, off_t offset);\n> > > > +\tint munmap(void *addr, size_t length);\n> > > > +\tint ioctl(int fd, unsigned long request, void *arg);\n> > > > +\n> > > > +\tstatic int bplMultiplier(unsigned int format);\n> > > > +\tstatic int imageSize(unsigned int format, unsigned int width,\n> > > > +\t\t\t     unsigned int height);\n> > > > +\n> > > > +\tstatic unsigned int v4l2ToDrm(unsigned int pixelformat);\n> > > > +\tstatic unsigned int drmToV4L2(unsigned int pixelformat);\n> > \n> > Those four methods are now used by the V4L2CameraProxy class only, you\n> > can move them there and make them private.\n> > \n> > > > +\n> > > > +private:\n> > > > +\tV4L2CompatManager();\n> > > > +\t~V4L2CompatManager();\n> > > > +\n> > > > +\tvoid run() override;\n> > > > +\tint getCameraIndex(int fd);\n> > > > +\n> > > > +\ttypedef int (*openat_func_t)(int dirfd, const char *path, int oflag, ...);\n> > > > +\ttypedef int (*dup_func_t)(int oldfd);\n> > > > +\ttypedef int (*close_func_t)(int fd);\n> > > > +\ttypedef int (*ioctl_func_t)(int fd, unsigned long request, ...);\n> > > > +\ttypedef void *(*mmap_func_t)(void *addr, size_t length, int prot,\n> > > > +\t\t\t\t     int flags, int fd, off_t offset);\n> > > > +\ttypedef int (*munmap_func_t)(void *addr, size_t length);\n> > > > +\n> > > > +\topenat_func_t openat_func_;\n> > > > +\tdup_func_t    dup_func_;\n> > > > +\tclose_func_t  close_func_;\n> > > > +\tioctl_func_t  ioctl_func_;\n> > > > +\tmmap_func_t   mmap_func_;\n> > > > +\tmunmap_func_t munmap_func_;\n> > > > +\n> > > > +\tCameraManager *cm_;\n> > > > +\n> > > > +\tstd::mutex mutex_;\n> > > > +\tstd::condition_variable cv_;\n> > > > +\tbool initialized_;\n> > > > +\n> > > > +\tstd::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;\n> > > > +\tstd::map<int, V4L2CameraProxy *> devices_;\n> > > > +\tstd::map<void *, V4L2CameraProxy *> mmaps_;\n> > > > +};\n> > > > +\n> > > > +#endif /* __V4L2_COMPAT_MANAGER_H__ */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A392B60469\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 31 Dec 2019 13:18:33 +0100 (CET)","from pendragon.ideasonboard.com (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DA1ADDF;\n\tTue, 31 Dec 2019 13:18:32 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1577794713;\n\tbh=IQzcUjTanCi1CYdJMmuOugQgUZ9W6taWyB1sA2b7A5k=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=wAd47W9r2ZQQf91SeBzHWYrD266Ts3IOmAWfUV1C+xnWP1wqd9ztM2y2HY/v4Xr0j\n\tF/Yr6VnQfjxVNmLBadxMXASu7Jc182gIf4Akx+vztdkZLepf568zUuh7q898fp4W8O\n\tlwyheabAhNo44PgcYghSiqC7dDIvkSqTFJXgR+uY=","Date":"Tue, 31 Dec 2019 14:18:22 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20191231121822.GA4888@pendragon.ideasonboard.com>","References":"<20191223072620.13022-1-paul.elder@ideasonboard.com>\n\t<20191223072620.13022-6-paul.elder@ideasonboard.com>\n\t<20191227145031.5abrggqog3pqjj5k@uno.localdomain>\n\t<20191228004007.GC5747@pendragon.ideasonboard.com>\n\t<20191231054041.GA24428@localhost.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20191231054041.GA24428@localhost.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 5/6] v4l2: v4l2_compat: Add V4L2\n\tcompatibility layer","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 31 Dec 2019 12:18:33 -0000"}}]