[{"id":3213,"web_url":"https://patchwork.libcamera.org/comment/3213/","msgid":"<20191209045821.GA1368@localhost.localdomain>","date":"2019-12-09T04:58:21","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> Note that until we have a way of mapping V4L2 device nodes to libcamera\n> cameras, the V4L2 compatibility layer will always selects and use the\n> first enumerated libcamera camera.\n\nI'm sorry, I just realized I forgot to update this description.\nIn this version we match the V4L2 device node to libcamera camera based\non the name exposed in sysfs for the device node, and with the libcamera\ncamera's exposed name.\n\n\nPaul\n\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> \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             |  30 ++\n>  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n>  src/v4l2/v4l2_camera.h           |  67 +++++\n>  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n>  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n>  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n>  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n>  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n>  10 files changed, 1469 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..1650048e\n> --- /dev/null\n> +++ b/src/v4l2/meson.build\n> @@ -0,0 +1,30 @@\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> +    '-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..f944c577\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -0,0 +1,299 @@\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> +#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 \"v4l2_compat_manager.h\"\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DECLARE_CATEGORY(V4L2Compat);\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> +V4L2Camera::~V4L2Camera()\n> +{\n> +\twhile (!pendingRequests_.empty()) {\n> +\t\tdelete pendingRequests_.front();\n> +\t\tpendingRequests_.pop();\n> +\t}\n> +\n> +\tbufferLock_.lock();\n> +\twhile (!completedBuffers_.empty()) {\n> +\t\tdelete completedBuffers_.front();\n> +\t\tcompletedBuffers_.pop();\n> +\t}\n> +\tbufferLock_.unlock();\n> +\n> +\tcamera_->release();\n> +}\n> +\n> +void V4L2Camera::open(int *ret, bool nonblock)\n> +{\n> +\tnonblock_ = nonblock;\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_ == nullptr) {\n> +\t\t*ret = -EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\tupdateSizeImage();\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::updateSizeImage()\n> +{\n> +\tStreamConfiguration &streamConfig = config_->at(0);\n> +\tsizeimage_ =\n> +\t\tV4L2CompatManager::imageSize(\n> +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> +\t\t\tstreamConfig.size.width,\n> +\t\t\tstreamConfig.size.height);\n> +}\n> +\n> +void V4L2Camera::requestComplete(Request *request)\n> +{\n> +\tif (request->status() == Request::RequestCancelled) {\n> +\t\tLOG(V4L2Compat, Error) << \"Request not succesfully completed: \"\n> +\t\t\t\t       << request->status();\n> +\t\treturn;\n> +\t}\n> +\n> +\t/* We only have one stream at the moment. */\n> +\tbufferLock_.lock();\n> +\tBuffer *buffer = request->buffers().begin()->second;\n> +\tcompletedBuffers_.push(buffer);\n> +\tbufferLock_.unlock();\n> +\n> +\tbufferSema_.release();\n> +}\n> +\n> +void V4L2Camera::configure(int *ret, struct v4l2_format *arg,\n> +\t\t\t   unsigned int bufferCount)\n> +{\n> +\tStreamConfiguration &streamConfig = config_->at(0);\n> +\tstreamConfig.size.width = arg->fmt.pix.width;\n> +\tstreamConfig.size.height = arg->fmt.pix.height;\n> +\tstreamConfig.pixelFormat =\n> +\t\tV4L2CompatManager::v4l2ToDrm(arg->fmt.pix.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> +\tupdateSizeImage();\n> +\tif (sizeimage_ == 0)\n> +\t\t*ret = -EINVAL;\n> +}\n> +\n> +void V4L2Camera::mmap(void **ret, int *err, void *addr, size_t length, int prot, off_t offset)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> +\n> +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> +\t\t*ret = MAP_FAILED;\n> +\t\t*err = ENOTSUP;\n> +\t\treturn;\n> +\t}\n> +\n> +\tunsigned int index = offset / sizeimage_;\n> +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> +\t\t*ret = MAP_FAILED;\n> +\t\t*err = EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\tStream *stream = *camera_->streams().begin();\n> +\t*ret = stream->buffers()[index].planes()[0].mem();\n> +\t*err = 0;\n> +}\n> +\n> +void V4L2Camera::munmap(int *ret, void *addr, size_t length)\n> +{\n> +\t*ret = 0;\n> +\n> +\tif (length != sizeimage_)\n> +\t\t*ret = -EINVAL;\n> +}\n> +\n> +void V4L2Camera::validStreamType(bool *ret, uint32_t type)\n> +{\n> +\t*ret = (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> +}\n> +\n> +void V4L2Camera::validMemoryType(bool *ret, uint32_t memory)\n> +{\n> +\t*ret = (memory == V4L2_MEMORY_MMAP);\n> +}\n> +\n> +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\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> +\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> +\twhile (!pendingRequests_.empty()) {\n> +\t\t*ret = camera_->queueRequest(pendingRequests_.front());\n> +\t\tpendingRequests_.pop();\n> +\t\tif (*ret < 0)\n> +\t\t\treturn;\n> +\t}\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, struct v4l2_buffer *arg)\n> +{\n> +\tStream *stream = config_->at(0).stream();\n> +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(arg->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> +\tRequest *request = camera_->createRequest();\n> +\tif (request == nullptr) {\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(request);\n> +\t} else {\n> +\t\t*ret = camera_->queueRequest(request);\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> +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> +\t*ret = 0;\n> +}\n> +\n> +int V4L2Camera::dqbuf(struct v4l2_buffer *arg)\n> +{\n> +\tif (nonblock_ && !bufferSema_.tryAcquire())\n> +\t\treturn -EAGAIN;\n> +\telse\n> +\t\tbufferSema_.acquire();\n> +\n> +\tbufferLock_.lock();\n> +\tBuffer *buffer = completedBuffers_.front();\n> +\tcompletedBuffers_.pop();\n> +\tbufferLock_.unlock();\n> +\n> +\targ->bytesused = buffer->bytesused();\n> +\targ->field = V4L2_FIELD_NONE;\n> +\targ->timestamp.tv_sec = buffer->timestamp() / 1000000000;\n> +\targ->timestamp.tv_usec = buffer->timestamp() / 1000;\n> +\targ->sequence = buffer->sequence();\n> +\n> +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> +\n> +\targ->length = sizeimage_;\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..73a427c4\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -0,0 +1,67 @@\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> +#ifndef __V4L2_CAMERA_H__\n> +#define __V4L2_CAMERA_H__\n> +\n> +#include <linux/videodev2.h>\n> +#include <mutex>\n> +#include <queue>\n> +\n> +#include <libcamera/camera.h>\n> +#include \"semaphore.h\"\n> +\n> +using namespace libcamera;\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, bool nonblock);\n> +\tvoid close(int *ret);\n> +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> +\tvoid requestComplete(Request *request);\n> +\n> +\tvoid mmap(void **ret, int *err, void *addr, size_t length,\n> +\t\t  int prot, off_t offset);\n> +\tvoid munmap(int *ret, void *addr, size_t length);\n> +\n> +\tvoid configure(int *ret, struct v4l2_format *arg,\n> +\t\t       unsigned int bufferCount);\n> +\n> +\tvoid validStreamType(bool *ret, uint32_t type);\n> +\tvoid validMemoryType(bool *ret, uint32_t memory);\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, struct v4l2_buffer *arg);\n> +\tint dqbuf(struct v4l2_buffer *arg);\n> +\n> +private:\n> +\tvoid updateSizeImage();\n> +\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tstd::unique_ptr<CameraConfiguration> config_;\n> +\n> +\tunsigned int bufferCount_;\n> +\tbool isRunning_;\n> +\tbool nonblock_;\n> +\n> +\tunsigned int sizeimage_;\n> +\n> +\tSemaphore bufferSema_;\n> +\tstd::mutex bufferLock_;\n> +\n> +\tstd::queue<Request *> pendingRequests_;\n> +\tstd::queue<Buffer *> 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..66d558e4\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> @@ -0,0 +1,452 @@\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> +#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> +V4L2CameraProxy::~V4L2CameraProxy()\n> +{\n> +}\n> +\n> +int V4L2CameraProxy::open(bool nonblock)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing OPEN\";\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> +\t\t\t    &ret, nonblock);\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> +\tsetFmtFromConfig();\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::close()\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing CLOSE\";\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> +\tvoid *val;\n> +\tint err;\n> +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> +\t\t\t    &val, &err, addr, length, prot, offset);\n> +\tif (val == MAP_FAILED)\n> +\t\terrno = err;\n> +\treturn val;\n> +}\n> +\n> +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing MUNMAP\";\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::munmap, ConnectionTypeBlocking,\n> +\t\t\t    &ret, addr, length);\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> +\n> +bool V4L2CameraProxy::hasPixelFormat(unsigned int format)\n> +{\n> +\tconst std::vector<PixelFormat> &formats =\n> +\t\tstreamConfig_.formats().pixelformats();\n> +\treturn std::find(formats.begin(), formats.end(), format) != formats.end();\n> +}\n> +\n> +/* \\todo getDeviceCaps? getMemoryCaps? */\n> +\n> +bool V4L2CameraProxy::hasSize(unsigned int format, Size size)\n> +{\n> +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> +\treturn std::find(sizes.begin(), sizes.end(), size) != sizes.end();\n> +}\n> +\n> +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> +{\n> +\tbool valid;\n> +\tvcam_->invokeMethod(&V4L2Camera::validStreamType,\n> +\t\t\t    ConnectionTypeBlocking, &valid, type);\n> +\tif (!valid)\n> +\t\treturn false;\n> +\n> +\treturn true;\n> +}\n> +\n> +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> +{\n> +\tbool valid;\n> +\tvcam_->invokeMethod(&V4L2Camera::validMemoryType,\n> +\t\t\t    ConnectionTypeBlocking, &valid, memory);\n> +\tif (!valid)\n> +\t\treturn false;\n> +\n> +\treturn true;\n> +}\n> +\n> +void V4L2CameraProxy::setFmtFromConfig()\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> +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> +\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> +\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\treturn -EINVAL;\n> +\tif (arg->index > streamConfig_.formats().pixelformats().size())\n> +\t\treturn -EINVAL;\n> +\n> +\tmemcpy(arg->description, \"asdf\", 5);\n> +\targ->pixelformat =\n> +\t\tV4L2CompatManager::drmToV4L2(\n> +\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> +\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,\n> +\t\t\t    &ret, arg, bufferCount_);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> +\tsetFmtFromConfig();\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> +\tif (!hasPixelFormat(format))\n> +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> +\n> +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> +\tif (!hasSize(format, size))\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> +\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\treturn -EINVAL;\n> +\tif (!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,\n> +\t\t\t    &ret, &curV4L2Format_, arg->count);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\targ->count = streamConfig_.bufferCount;\n> +\tbufferCount_ = arg->count;\n> +\n> +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> +\t\treturn -EINVAL;\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\treturn -EINVAL;\n> +\tif (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\treturn -EINVAL;\n> +\tif (!validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\tif (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);\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\treturn -EINVAL;\n> +\tif (!validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\n> +\targ->index = currentBuf_;\n> +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> +\n> +\treturn vcam_->dqbuf(arg);\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> +\tcase VIDIOC_EXPBUF:\n> +\tcase VIDIOC_ENUM_FRAMESIZES:\n> +\tdefault:\n> +\t\tret = -ENOTTY;\n> +\t}\n> +\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\terrno = 0;\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..64c7aadd\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.h\n> @@ -0,0 +1,63 @@\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> +#ifndef __V4L2_CAMERA_PROXY_H__\n> +#define __V4L2_CAMERA_PROXY_H__\n> +\n> +#include <linux/videodev2.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> +\t~V4L2CameraProxy();\n> +\n> +\tint open(bool nonblock);\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> +private:\n> +\tbool hasPixelFormat(unsigned int format);\n> +\tbool hasSize(unsigned int format, Size size);\n> +\tbool validateStreamType(uint32_t type);\n> +\tbool validateMemoryType(uint32_t memory);\n> +\tvoid setFmtFromConfig();\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 index_;\n> +\n> +\tstruct v4l2_format curV4L2Format_;\n> +\tStreamConfiguration streamConfig_;\n> +\tstruct v4l2_capability capabilities_;\n> +\tunsigned int bufferCount_;\n> +\tunsigned int currentBuf_;\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..3330e7bc\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat.cpp\n> @@ -0,0 +1,81 @@\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 <iostream>\n> +\n> +#include <errno.h>\n> +#include <fcntl.h>\n> +#include <linux/videodev2.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> +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 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> +\tvoid *val = V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> +\treturn val;\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..90416b35\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat_manager.cpp\n> @@ -0,0 +1,382 @@\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> +#include \"v4l2_compat_manager.h\"\n> +\n> +#include <dlfcn.h>\n> +#include <fcntl.h>\n> +#include <fstream>\n> +#include <iostream>\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)\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> +\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);\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\n> +\t * gets created here to wraps 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\n> +\t * as we're now ready to handle calls from the framework.\n> +\t */\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 v4l2CompatManager;\n> +\treturn &v4l2CompatManager;\n> +}\n> +\n> +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> +{\n> +\tauto device = devices_.find(fd);\n> +\tif (device == devices_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn device->second.get();\n> +}\n> +\n> +V4L2CameraProxy *V4L2CompatManager::getCamera(void *addr)\n> +{\n> +\tauto map = mmaps_.find(addr);\n> +\tif (map == mmaps_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn devices_.at(map->second).get();\n> +}\n> +\n> +int V4L2CompatManager::getCameraIndex(int fd)\n> +{\n> +\tstruct stat statbuf;\n> +\tfstat(fd, &statbuf);\n> +\tunsigned int dev_major = major(statbuf.st_rdev);\n> +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n> +\n> +\tstd::string name;\n> +\tstd::ifstream nameFile;\n> +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> +\t\t      std::to_string(dev_minor) + \"/name\");\n> +\tif (!nameFile)\n> +\t\treturn -1;\n> +\n> +\tstd::getline(nameFile, name);\n> +\tnameFile.close();\n> +\n> +\tunsigned int index = 0;\n> +\tfor (auto &camera : cm_->cameras()) {\n> +\t\tif (!camera->name().compare(name))\n> +\t\t\tbreak;\n> +\t\t++index;\n> +\t}\n> +\n> +\tif (index >= cm_->cameras().size())\n> +\t\treturn -1;\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> +\tif (std::string(path).find(\"video\") == std::string::npos)\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, Error) << \"No camera found for \" << path;\n> +\t\treturn fd;\n> +\t}\n> +\n> +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> +\n> +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> +\tret = proxy->open(mode & O_NONBLOCK);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & 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_[newfd] = devices_[oldfd];\n> +\n> +\treturn newfd;\n> +}\n> +\n> +int V4L2CompatManager::close(int fd)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(fd);\n> +\tif (proxy)\n> +\t\treturn proxy->close();\n> +\n> +\tint ret = close_func_(fd);\n> +\treturn ret;\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_[addr] = fd;\n> +\treturn map;\n> +}\n> +\n> +int V4L2CompatManager::munmap(void *addr, size_t length)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(addr);\n> +\tif (!proxy)\n> +\t\treturn munmap_func_(addr, length);\n> +\n> +\tint ret = proxy->munmap(addr, length);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tmmaps_.erase(addr);\n> +\taddr = nullptr;\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..d5ae7810\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> +#ifndef __V4L2_COMPAT_MANAGER_H__\n> +#define __V4L2_COMPAT_MANAGER_H__\n> +\n> +#include <condition_variable>\n> +#include <linux/videodev2.h>\n> +#include <map>\n> +#include <mutex>\n> +#include <queue>\n> +#include <sys/syscall.h>\n> +#include <unistd.h>\n> +#include <vector>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/camera_manager.h>\n> +#include <libcamera/stream.h>\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> +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> +\n> +\tV4L2CameraProxy *getCamera(int fd);\n> +\tV4L2CameraProxy *getCamera(void *addr);\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> +\n> +\tstd::vector<std::shared_ptr<V4L2CameraProxy>> proxies_;\n> +\tstd::map<int, std::shared_ptr<V4L2CameraProxy>> devices_;\n> +\tstd::map<void *, int> mmaps_;\n> +};\n> +\n> +#endif /* __V4L2_COMPAT_MANAGER_H__ */\n> -- \n> 2.23.0\n>","headers":{"Return-Path":"<paul.elder@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 44A8A60BFE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  9 Dec 2019 05:58:27 +0100 (CET)","from localhost.localdomain (unknown [96.44.9.94])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 49E5C52B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  9 Dec 2019 05:58:26 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1575867506;\n\tbh=qbQGJNPNHYNn+B+Y7fQA5h9e7bRfJMDJik2vtfT9vcc=;\n\th=Date:From:To:Subject:References:In-Reply-To:From;\n\tb=YDgu7qywiR9bbECYUjl/ZHgAdznOqzRT34BQNMRpfrGWidhTjOoiortQEX37uXsS2\n\tWxuYgCzJtolQk9k0r3Rdjr87fqaULf2xvzxpg/H2rqdNePcm2ToZnOx0KzWy1i3Yz2\n\tLdBM4h3gxFym35BQSA9f35+KJuSCgqH6lXEWdwLk=","Date":"Sun, 8 Dec 2019 23:58:21 -0500","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191209045821.GA1368@localhost.localdomain>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20191209045603.6245-2-paul.elder@ideasonboard.com>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 09 Dec 2019 04:58:27 -0000"}},{"id":3214,"web_url":"https://patchwork.libcamera.org/comment/3214/","msgid":"<20191209103013.jw26qssnewhuumux@uno.localdomain>","date":"2019-12-09T10:30:13","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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, thanks for the quick follow up\n\nOn Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> Note that until we have a way of mapping V4L2 device nodes to libcamera\n> cameras, the V4L2 compatibility layer will always selects and use the\n> first enumerated libcamera camera.\n>\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n>\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             |  30 ++\n>  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n>  src/v4l2/v4l2_camera.h           |  67 +++++\n>  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n>  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n>  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n>  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n>  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n>  10 files changed, 1469 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..1650048e\n> --- /dev/null\n> +++ b/src/v4l2/meson.build\n> @@ -0,0 +1,30 @@\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> +    '-fvisibility=hidden',\n> +]\n> +\n> +v4l2_compat = shared_library('v4l2-compat',\n> +                             v4l2_compat_sources,\n> +                             name_prefix : '',\n\nDo you need this ? I tried removing it and it succesfully compiled...\n\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..f944c577\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -0,0 +1,299 @@\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> +#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 \"v4l2_compat_manager.h\"\n> +\n> +using namespace libcamera;\n> +\n> +LOG_DECLARE_CATEGORY(V4L2Compat);\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> +V4L2Camera::~V4L2Camera()\n> +{\n> +\twhile (!pendingRequests_.empty()) {\n> +\t\tdelete pendingRequests_.front();\n> +\t\tpendingRequests_.pop();\n> +\t}\n> +\n> +\tbufferLock_.lock();\n> +\twhile (!completedBuffers_.empty()) {\n> +\t\tdelete completedBuffers_.front();\n> +\t\tcompletedBuffers_.pop();\n> +\t}\n> +\tbufferLock_.unlock();\n\nIs locking required here ? The V4L2Camera is destroyed when the proxy\nis destroyed, no other calls should be in-flight. Are all buffers\ndequeued at this point ?\n\n> +\n> +\tcamera_->release();\n> +}\n> +\n> +void V4L2Camera::open(int *ret, bool nonblock)\n> +{\n> +\tnonblock_ = nonblock;\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\nProbably roles should be handled differently, but I'm not sure how.\nDoes our API fall short here ? What if vewifinder is not supported,\nshould we be able to query what roles are supported from a camera ?\n\n> +\tif (config_ == nullptr) {\n> +\t\t*ret = -EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\tupdateSizeImage();\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::updateSizeImage()\n> +{\n> +\tStreamConfiguration &streamConfig = config_->at(0);\n> +\tsizeimage_ =\n> +\t\tV4L2CompatManager::imageSize(\n\nnit: this could fit on the previous line\n\n> +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> +\t\t\tstreamConfig.size.width,\n> +\t\t\tstreamConfig.size.height);\n> +}\n> +\n> +void V4L2Camera::requestComplete(Request *request)\n> +{\n> +\tif (request->status() == Request::RequestCancelled) {\n> +\t\tLOG(V4L2Compat, Error) << \"Request not succesfully completed: \"\n> +\t\t\t\t       << request->status();\n> +\t\treturn;\n> +\t}\n> +\n> +\t/* We only have one stream at the moment. */\n> +\tbufferLock_.lock();\n> +\tBuffer *buffer = request->buffers().begin()->second;\n> +\tcompletedBuffers_.push(buffer);\n> +\tbufferLock_.unlock();\n> +\n> +\tbufferSema_.release();\n> +}\n> +\n> +void V4L2Camera::configure(int *ret, struct v4l2_format *arg,\n> +\t\t\t   unsigned int bufferCount)\n> +{\n> +\tStreamConfiguration &streamConfig = config_->at(0);\n> +\tstreamConfig.size.width = arg->fmt.pix.width;\n> +\tstreamConfig.size.height = arg->fmt.pix.height;\n> +\tstreamConfig.pixelFormat =\n> +\t\tV4L2CompatManager::v4l2ToDrm(arg->fmt.pix.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> +\tupdateSizeImage();\n> +\tif (sizeimage_ == 0)\n\nI'm always a bit scared by functions that updates a global variable as\nside effect and let the caller responsability of making sure the\nintended variable is set to the desired value.\n\nI would rather make updateSizeImage() a calculateSizeImage(config) and\nassign it to the the global sizeimage_ in the caller, or make it\nreturn an error code.\n\n> +\t\t*ret = -EINVAL;\n> +}\n> +\n> +void V4L2Camera::mmap(void **ret, int *err, void *addr, size_t length, int prot, off_t offset)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> +\n> +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> +\t\t*ret = MAP_FAILED;\n> +\t\t*err = ENOTSUP;\n> +\t\treturn;\n> +\t}\n> +\n> +\tunsigned int index = offset / sizeimage_;\n> +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> +\t\t*ret = MAP_FAILED;\n> +\t\t*err = EINVAL;\n> +\t\treturn;\n> +\t}\n> +\n> +\tStream *stream = *camera_->streams().begin();\n> +\t*ret = stream->buffers()[index].planes()[0].mem();\n> +\t*err = 0;\n> +}\n> +\n> +void V4L2Camera::munmap(int *ret, void *addr, size_t length)\n> +{\n> +\t*ret = 0;\n> +\n> +\tif (length != sizeimage_)\n> +\t\t*ret = -EINVAL;\n> +}\n> +\n> +void V4L2Camera::validStreamType(bool *ret, uint32_t type)\n> +{\n> +\t*ret = (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> +}\n> +\n> +void V4L2Camera::validMemoryType(bool *ret, uint32_t memory)\n> +{\n> +\t*ret = (memory == V4L2_MEMORY_MMAP);\n> +}\n> +\n> +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n\nEmpty line, to be consistent with other functions.\n\nBut I actually wonder if we need this. i had the same in the android\nHAL and it's a useful tracing tool, so I understand you might want to\nkeep them. After all, debug can be filtered out...\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> +\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> +\twhile (!pendingRequests_.empty()) {\n> +\t\t*ret = camera_->queueRequest(pendingRequests_.front());\n> +\t\tpendingRequests_.pop();\n> +\t\tif (*ret < 0)\n> +\t\t\treturn;\n> +\t}\n\nNo error messages ? How does the error log looks like here ?\n\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, struct v4l2_buffer *arg)\n> +{\n> +\tStream *stream = config_->at(0).stream();\n> +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(arg->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> +\tRequest *request = camera_->createRequest();\n> +\tif (request == nullptr) {\n\nNit: could you unify on a single pattern ? My preference would be for\n        if (!request)\n\nand I think the style guied suggests it as well\n\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(request);\n> +\t} else {\n> +\t\t*ret = camera_->queueRequest(request);\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> +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> +\t*ret = 0;\n> +}\n> +\n> +int V4L2Camera::dqbuf(struct v4l2_buffer *arg)\n> +{\n> +\tif (nonblock_ && !bufferSema_.tryAcquire())\n> +\t\treturn -EAGAIN;\n> +\telse\n> +\t\tbufferSema_.acquire();\n> +\n> +\tbufferLock_.lock();\n> +\tBuffer *buffer = completedBuffers_.front();\n> +\tcompletedBuffers_.pop();\n> +\tbufferLock_.unlock();\n> +\n> +\targ->bytesused = buffer->bytesused();\n> +\targ->field = V4L2_FIELD_NONE;\n> +\targ->timestamp.tv_sec = buffer->timestamp() / 1000000000;\n> +\targ->timestamp.tv_usec = buffer->timestamp() / 1000;\n> +\targ->sequence = buffer->sequence();\n> +\n> +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n\nShouldn't the FLAG_DONE bit be set ?\n\n> +\n> +\targ->length = sizeimage_;\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..73a427c4\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -0,0 +1,67 @@\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> +#ifndef __V4L2_CAMERA_H__\n> +#define __V4L2_CAMERA_H__\n> +\n> +#include <linux/videodev2.h>\n> +#include <mutex>\n> +#include <queue>\n> +\n> +#include <libcamera/camera.h>\n> +#include \"semaphore.h\"\n> +\n> +using namespace libcamera;\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, bool nonblock);\n> +\tvoid close(int *ret);\n> +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> +\tvoid requestComplete(Request *request);\n> +\n> +\tvoid mmap(void **ret, int *err, void *addr, size_t length,\n> +\t\t  int prot, off_t offset);\n> +\tvoid munmap(int *ret, void *addr, size_t length);\n> +\n> +\tvoid configure(int *ret, struct v4l2_format *arg,\n> +\t\t       unsigned int bufferCount);\n> +\n> +\tvoid validStreamType(bool *ret, uint32_t type);\n> +\tvoid validMemoryType(bool *ret, uint32_t memory);\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, struct v4l2_buffer *arg);\n> +\tint dqbuf(struct v4l2_buffer *arg);\n> +\n> +private:\n> +\tvoid updateSizeImage();\n> +\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tstd::unique_ptr<CameraConfiguration> config_;\n> +\n> +\tunsigned int bufferCount_;\n> +\tbool isRunning_;\n> +\tbool nonblock_;\n> +\n> +\tunsigned int sizeimage_;\n> +\n> +\tSemaphore bufferSema_;\n> +\tstd::mutex bufferLock_;\n> +\n> +\tstd::queue<Request *> pendingRequests_;\n> +\tstd::queue<Buffer *> 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..66d558e4\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> @@ -0,0 +1,452 @@\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> +#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> +V4L2CameraProxy::~V4L2CameraProxy()\n> +{\n> +}\n\nDo you need to declare and empty destructor ?\n\n> +\n> +int V4L2CameraProxy::open(bool nonblock)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing OPEN\";\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> +\t\t\t    &ret, nonblock);\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> +\tsetFmtFromConfig();\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2CameraProxy::close()\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing CLOSE\";\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> +\tvoid *val;\n> +\tint err;\n> +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> +\t\t\t    &val, &err, addr, length, prot, offset);\n> +\tif (val == MAP_FAILED)\n> +\t\terrno = err;\n> +\treturn val;\n> +}\n> +\n> +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> +{\n> +\tLOG(V4L2Compat, Debug) << \"Servicing MUNMAP\";\n> +\n> +\tint ret;\n> +\tvcam_->invokeMethod(&V4L2Camera::munmap, ConnectionTypeBlocking,\n> +\t\t\t    &ret, addr, length);\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> +\n> +bool V4L2CameraProxy::hasPixelFormat(unsigned int format)\n> +{\n> +\tconst std::vector<PixelFormat> &formats =\n> +\t\tstreamConfig_.formats().pixelformats();\n> +\treturn std::find(formats.begin(), formats.end(), format) != formats.end();\n> +}\n> +\n> +/* \\todo getDeviceCaps? getMemoryCaps? */\n> +\n> +bool V4L2CameraProxy::hasSize(unsigned int format, Size size)\n> +{\n> +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> +\treturn std::find(sizes.begin(), sizes.end(), size) != sizes.end();\n> +}\n> +\n> +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> +{\n> +\tbool valid;\n> +\tvcam_->invokeMethod(&V4L2Camera::validStreamType,\n> +\t\t\t    ConnectionTypeBlocking, &valid, type);\n> +\tif (!valid)\n> +\t\treturn false;\n> +\n> +\treturn true;\n\nTo answer you question on the reply to v1: if you prefer this stlye,\nthen it's fine with me. I would have\n        return valid;\nand that's it\n\n> +}\n> +\n> +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> +{\n> +\tbool valid;\n> +\tvcam_->invokeMethod(&V4L2Camera::validMemoryType,\n> +\t\t\t    ConnectionTypeBlocking, &valid, memory);\n> +\tif (!valid)\n> +\t\treturn false;\n> +\n> +\treturn true;\n> +}\n> +\n> +void V4L2CameraProxy::setFmtFromConfig()\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> +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> +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n\nThis should come from some library wide header I guess. Having it\nhard-coded here won't scale. Not for this series probably, but is\nworth a todo I guess\n\n> +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> +\tcapabilities_.capabilities =\n> +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\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\treturn -EINVAL;\n> +\tif (arg->index > streamConfig_.formats().pixelformats().size())\n> +\t\treturn -EINVAL;\n> +\n> +\tmemcpy(arg->description, \"asdf\", 5);\n\nIf we don't want to introduce the format->description map now, add a\ntodo please\n\n> +\targ->pixelformat =\n> +\t\tV4L2CompatManager::drmToV4L2(\n\nNit: fits on the previous line\n\n> +\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> +\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,\n> +\t\t\t    &ret, arg, bufferCount_);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\n> +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> +\tsetFmtFromConfig();\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> +\tif (!hasPixelFormat(format))\n> +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> +\n> +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> +\tif (!hasSize(format, size))\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\nNit: hard to parse\n\n\targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n\t\t\t                V4L2CompatManager::drmToV4L2(format)) *\n                                        arg->fmt.pix.width;\n\n ?\n\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> +\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\treturn -EINVAL;\n> +\tif (!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,\n> +\t\t\t    &ret, &curV4L2Format_, arg->count);\n> +\tif (ret < 0)\n> +\t\treturn -EINVAL;\n> +\targ->count = streamConfig_.bufferCount;\n> +\tbufferCount_ = arg->count;\n> +\n> +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> +\t\treturn -EINVAL;\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\treturn -EINVAL;\n> +\tif (arg->index >= stream->buffers().size())\n> +\t\treturn -EINVAL;\n\nNit: could be made a single condition, up to you\n\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\nSo, the validation of the buffer and memory type is performed by\ninvoking a method on the V4L2Camera, while we here hardcode them in\nthe proxy... Not big, but I would rather keep the information here or\nthere (I would drop the method call for the validation, as so far we\nonly support one type)\n\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\treturn -EINVAL;\n> +\tif (!validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\tif (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);\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\treturn -EINVAL;\n> +\tif (!validateMemoryType(arg->memory))\n> +\t\treturn -EINVAL;\n> +\n> +\targ->index = currentBuf_;\n> +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> +\n> +\treturn vcam_->dqbuf(arg);\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> +\tcase VIDIOC_EXPBUF:\n> +\tcase VIDIOC_ENUM_FRAMESIZES:\n\nAren't there more ioctl calls ? Why are these two special ? Can't they\nbe catched by default like the others ?\n\n> +\tdefault:\n> +\t\tret = -ENOTTY;\n> +\t}\n> +\n> +\tif (ret < 0) {\n> +\t\terrno = -ret;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\terrno = 0;\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..64c7aadd\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_camera_proxy.h\n> @@ -0,0 +1,63 @@\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> +#ifndef __V4L2_CAMERA_PROXY_H__\n> +#define __V4L2_CAMERA_PROXY_H__\n> +\n> +#include <linux/videodev2.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> +\t~V4L2CameraProxy();\n> +\n> +\tint open(bool nonblock);\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> +private:\n> +\tbool hasPixelFormat(unsigned int format);\n> +\tbool hasSize(unsigned int format, Size size);\n> +\tbool validateStreamType(uint32_t type);\n> +\tbool validateMemoryType(uint32_t memory);\n> +\tvoid setFmtFromConfig();\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 index_;\n> +\n> +\tstruct v4l2_format curV4L2Format_;\n> +\tStreamConfiguration streamConfig_;\n> +\tstruct v4l2_capability capabilities_;\n> +\tunsigned int bufferCount_;\n> +\tunsigned int currentBuf_;\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..3330e7bc\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat.cpp\n> @@ -0,0 +1,81 @@\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 <iostream>\n> +\n> +#include <errno.h>\n> +#include <fcntl.h>\n> +#include <linux/videodev2.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> +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 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> +\tvoid *val = V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> +\treturn val;\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..90416b35\n> --- /dev/null\n> +++ b/src/v4l2/v4l2_compat_manager.cpp\n> @@ -0,0 +1,382 @@\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> +#include \"v4l2_compat_manager.h\"\n> +\n> +#include <dlfcn.h>\n> +#include <fcntl.h>\n> +#include <fstream>\n> +#include <iostream>\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)\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> +\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);\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\n> +\t * gets created here to wraps 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\n> +\t * as we're now ready to handle calls from the framework.\n> +\t */\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 v4l2CompatManager;\n> +\treturn &v4l2CompatManager;\n> +}\n> +\n> +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> +{\n> +\tauto device = devices_.find(fd);\n> +\tif (device == devices_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn device->second.get();\n> +}\n> +\n> +V4L2CameraProxy *V4L2CompatManager::getCamera(void *addr)\n> +{\n> +\tauto map = mmaps_.find(addr);\n> +\tif (map == mmaps_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn devices_.at(map->second).get();\n> +}\n> +\n> +int V4L2CompatManager::getCameraIndex(int fd)\n> +{\n> +\tstruct stat statbuf;\n> +\tfstat(fd, &statbuf);\n> +\tunsigned int dev_major = major(statbuf.st_rdev);\n> +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n> +\n> +\tstd::string name;\n> +\tstd::ifstream nameFile;\n> +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> +\t\t      std::to_string(dev_minor) + \"/name\");\n> +\tif (!nameFile)\n> +\t\treturn -1;\n> +\n> +\tstd::getline(nameFile, name);\n> +\tnameFile.close();\n> +\n> +\tunsigned int index = 0;\n> +\tfor (auto &camera : cm_->cameras()) {\n> +\t\tif (!camera->name().compare(name))\n> +\t\t\tbreak;\n> +\t\t++index;\n> +\t}\n> +\n> +\tif (index >= cm_->cameras().size())\n> +\t\treturn -1;\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> +\tif (std::string(path).find(\"video\") == std::string::npos)\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, Error) << \"No camera found for \" << path;\n> +\t\treturn fd;\n> +\t}\n> +\n> +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> +\n> +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n\nYou here share the ownership of the managed object owned by\nproxies_[camera_index] with a local variable, increasing its\nreference count\n\n> +\tret = proxy->open(mode & O_NONBLOCK);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> +\tif (efd < 0)\n> +\t\treturn efd;\n\nI'm still not convinced about this, I'll go read your reply to v1\nagain.\n\n> +\n> +\tdevices_.emplace(efd, proxy);\n\nAnd here you store it permanently in a map, making its reference count\npermanently = 2 (it goes up to 3, then the local variable goes out of\nscope and gest back to 2).\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_[newfd] = devices_[oldfd];\n\nThen here you create a new copy, with reference count = 3\n\nAt delete time you clear all the entries in devices_, causing the\nshared pointer stored in proxies_ to get back to 1, then proxies_ goes\nout of scope, and the managed object gets deleted.\n\nWhy all this dance ? Is the ownership of the CameraProxy shared\nbetween different componentes? Is it created or destoryed outside of\nthis class ? I would save all this complexity and store\nV4L2CameraProxy instances in a vector, and point to them in the map at\nopenat() time.\n\nThe only thing you care about is to increase the reference count to\nthe Camera, but you have a shared_ptr<> class instance in V4L2Camera,\nso that's fine.\n\n> +\n> +\treturn newfd;\n> +}\n> +\n> +int V4L2CompatManager::close(int fd)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(fd);\n> +\tif (proxy)\n> +\t\treturn proxy->close();\n> +\n> +\tint ret = close_func_(fd);\n> +\treturn ret;\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\nAlign to open ( please\n\nMostly stylistic comments, so I think we're almost there, at least\nfrom my side!\n\nThanks\n   j\n\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_[addr] = fd;\n> +\treturn map;\n> +}\n> +\n> +int V4L2CompatManager::munmap(void *addr, size_t length)\n> +{\n> +\tV4L2CameraProxy *proxy = getCamera(addr);\n> +\tif (!proxy)\n> +\t\treturn munmap_func_(addr, length);\n> +\n> +\tint ret = proxy->munmap(addr, length);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tmmaps_.erase(addr);\n> +\taddr = nullptr;\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..d5ae7810\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> +#ifndef __V4L2_COMPAT_MANAGER_H__\n> +#define __V4L2_COMPAT_MANAGER_H__\n> +\n> +#include <condition_variable>\n> +#include <linux/videodev2.h>\n> +#include <map>\n> +#include <mutex>\n> +#include <queue>\n> +#include <sys/syscall.h>\n> +#include <unistd.h>\n> +#include <vector>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/camera_manager.h>\n> +#include <libcamera/stream.h>\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> +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> +\n> +\tV4L2CameraProxy *getCamera(int fd);\n> +\tV4L2CameraProxy *getCamera(void *addr);\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> +\n> +\tstd::vector<std::shared_ptr<V4L2CameraProxy>> proxies_;\n> +\tstd::map<int, std::shared_ptr<V4L2CameraProxy>> devices_;\n> +\tstd::map<void *, int> 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 relay2-d.mail.gandi.net (relay2-d.mail.gandi.net\n\t[217.70.183.194])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2187460C0A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  9 Dec 2019 11:28:07 +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 relay2-d.mail.gandi.net (Postfix) with ESMTPSA id 5233140015;\n\tMon,  9 Dec 2019 10:28:06 +0000 (UTC)"],"X-Originating-IP":"93.34.114.233","Date":"Mon, 9 Dec 2019 11:30:13 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191209103013.jw26qssnewhuumux@uno.localdomain>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"poaybikhyr4k4r47\"","Content-Disposition":"inline","In-Reply-To":"<20191209045603.6245-2-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 09 Dec 2019 10:28:07 -0000"}},{"id":3261,"web_url":"https://patchwork.libcamera.org/comment/3261/","msgid":"<20191216001844.GG4889@pendragon.ideasonboard.com>","date":"2019-12-16T00:18:44","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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 Mon, Dec 09, 2019 at 11:30:13AM +0100, Jacopo Mondi wrote:\n> On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> > Note that until we have a way of mapping V4L2 device nodes to libcamera\n> > cameras, the V4L2 compatibility layer will always selects and use the\n\ns/selects/select/\n\n> > first enumerated libcamera camera.\n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> >\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             |  30 ++\n> >  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n> >  src/v4l2/v4l2_camera.h           |  67 +++++\n> >  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n> >  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n> >  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> >  10 files changed, 1469 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\nHow about 'Compile the V4L2 adaptation layer', as it's not part of\nlibcamera.so ?\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..1650048e\n> > --- /dev/null\n> > +++ b/src/v4l2/meson.build\n> > @@ -0,0 +1,30 @@\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\nAs explained in my review of v1, I would prefer also defining\n_FILE_OFFSET_BITS explicitly.\n\n> > +    '-fvisibility=hidden',\n> > +]\n> > +\n> > +v4l2_compat = shared_library('v4l2-compat',\n> > +                             v4l2_compat_sources,\n> > +                             name_prefix : '',\n> \n> Do you need this ? I tried removing it and it succesfully compiled...\n\nBut does it then generate a shared object without the 'lib' prefix ?\n\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\n[snip]\n\nSkipping over V4L2Camera and V4L2CameraProxy, I'll get to those\ntomorrow.\n\n> > diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> > new file mode 100644\n> > index 00000000..3330e7bc\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat.cpp\n> > @@ -0,0 +1,81 @@\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\"V4L2 adaptation layer\" ?\n\n> > + */\n> > +\n> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#include <iostream>\n\nYou don't need this header.\n\n> > +\n> > +#include <errno.h>\n> > +#include <fcntl.h>\n> > +#include <linux/videodev2.h>\n\nI don't think you need this header either.\n\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\nI would add a blank line here, as you add a blank line before the\ncorresponding }.\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 openat(AT_FDCWD, path, oflag, mode);\n\nYou could call\n\n\treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag,\n\t\t\t\t\t\t     mode);\n\nto avoid one layer of indirection.\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> > +\tvoid *val = V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> > +\treturn val;\n\nAnything wrong with\n\n\treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags,\n\t\t\t\t\t\t   fd, offset);\n\n?\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..90416b35\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > @@ -0,0 +1,382 @@\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\nMissing blank line.\n\n> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#include <dlfcn.h>\n> > +#include <fcntl.h>\n> > +#include <fstream>\n> > +#include <iostream>\n\nI don't think you need iostream here either.\n\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)\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\nYou can remove one space before the ) in each of those lines.\n\n> > +}\n> > +\n> > +V4L2CompatManager::~V4L2CompatManager()\n> > +{\n> > +\tdevices_.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);\n\nThere's a risk of a race condition here, where the new thread could\nnotify the condition variable before you start waiting on it. The wait\nwould then never complete. You need to add an initialized_ member to the\nclass, initialize it to false in the constructor, set it to true before\ncalling cv_.notify_one() in run(), and call\n\n\tcv_.wait(locker, []{ return initialized_; });\n\nhere.\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\n> > +\t * gets created here to wraps a camera device.\n\ns/wraps/wrap/\n\nand you can go up to 80 columns before wrapping.\n\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\n> > +\t * as we're now ready to handle calls from the framework.\n\nYou can go up to 80 columns before wrapping here too.\n\n> > +\t */\n\n\tmutex_.lock();\n\tinitialized_ = true;\n\tmutex_.unlock();\n\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 v4l2CompatManager;\n> > +\treturn &v4l2CompatManager;\n\ns/v4l2CompatManager/instance/ ?\n\n> > +}\n> > +\n> > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> > +{\n> > +\tauto device = devices_.find(fd);\n> > +\tif (device == devices_.end())\n> > +\t\treturn nullptr;\n> > +\n> > +\treturn device->second.get();\n> > +}\n> > +\n> > +V4L2CameraProxy *V4L2CompatManager::getCamera(void *addr)\n> > +{\n> > +\tauto map = mmaps_.find(addr);\n> > +\tif (map == mmaps_.end())\n> > +\t\treturn nullptr;\n> > +\n> > +\treturn devices_.at(map->second).get();\n> > +}\n> > +\n> > +int V4L2CompatManager::getCameraIndex(int fd)\n> > +{\n> > +\tstruct stat statbuf;\n> > +\tfstat(fd, &statbuf);\n> > +\tunsigned int dev_major = major(statbuf.st_rdev);\n> > +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n\ndevMajor and devMinor.\n\n> > +\n> > +\tstd::string name;\n> > +\tstd::ifstream nameFile;\n> > +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> > +\t\t      std::to_string(dev_minor) + \"/name\");\n> > +\tif (!nameFile)\n> > +\t\treturn -1;\n> > +\n> > +\tstd::getline(nameFile, name);\n> > +\tnameFile.close();\n> > +\n> > +\tunsigned int index = 0;\n> > +\tfor (auto &camera : cm_->cameras()) {\n> > +\t\tif (!camera->name().compare(name))\n> > +\t\t\tbreak;\n> > +\t\t++index;\n> > +\t}\n\nAs commented on v1, matching on the video node name isn't a good idea,\nas we want to support all cameras, and not all cameras are named based\non the video node name. I wrote\n\n\"One option would be to add a method to the camera manager to retrieve a\ncamera by device node path. This wouldn't expose any V4L2-specific\ninformation towards applications, the map of device nodes to cameras\nwould be private.\"\n\nbut I don't think that's right, we should instead retrieve a camera by\nmajor:minor, as multiple device nodes in the system could \n\n> > +\n> > +\tif (index >= cm_->cameras().size())\n> > +\t\treturn -1;\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> > +\tif (std::string(path).find(\"video\") == std::string::npos)\n> > +\t\treturn fd;\n\nSo opening \"/home/user/myvideo.mp4\" will match :-)\n\nYou can't match by name here, you need to fstat() and check that the\nfile is a character device node whose major is equal to the V4L2 major.\nOr, if you add a method to the camera manager to retrieve a camera by\nmajor:minor, I think you could even skip this check completely as it\nwould be handled in CameraManager (but the fstat is needed nonetheless).\n\n> > +\n> > +\tif (!isRunning())\n> > +\t\tinit();\n> > +\n> > +\tint ret = getCameraIndex(fd);\n> > +\tif (ret < 0) {\n> > +\t\tLOG(V4L2Compat, Error) << \"No camera found for \" << path;\n\nI'd turn that into an Info or Debug message as it's not necessarily an\nerror, not all cameras are supported by libcamera.\n\n> > +\t\treturn fd;\n> > +\t}\n\nYou need to close_func_(fd) here.\n\n> > +\n> > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > +\n> > +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> \n> You here share the ownership of the managed object owned by\n> proxies_[camera_index] with a local variable, increasing its\n> reference count\n> \n> > +\tret = proxy->open(mode & O_NONBLOCK);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> > +\tif (efd < 0)\n> > +\t\treturn efd;\n> \n> I'm still not convinced about this, I'll go read your reply to v1\n> again.\n\nWhat's wrong with it ? We need an fd that applications can use in a\nselect() or poll() call, so we can't return the fd for the video node,\nwe need an eventfd that we can signal when a request completes.\n\n> > +\n> > +\tdevices_.emplace(efd, proxy);\n> \n> And here you store it permanently in a map, making its reference count\n> permanently = 2 (it goes up to 3, then the local variable goes out of\n> scope and gest back to 2).\n\nI don't think this is a big issue, and reference counting is needed to\nsupport hot-unplug. As we don't support it yet we could turn proxies_\ninto a std::vector<std::unique_ptr<V4L2CameraProxy>> and devices_ into a\nstd::map<int, V4L2CameraProxy *>>, knowing that the device won't be\ndeleted until the V4L2CompatManager itself is deleted. The above lookup\nin proxies_ would then become\n\n\tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n\nI think the mmaps_ could then map from void * to V4L2CameraProxy *,\nwhich would avoid double look-ups in munmap().\n\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_[newfd] = devices_[oldfd];\n> \n> Then here you create a new copy, with reference count = 3\n> \n> At delete time you clear all the entries in devices_, causing the\n> shared pointer stored in proxies_ to get back to 1, then proxies_ goes\n> out of scope, and the managed object gets deleted.\n> \n> Why all this dance ? Is the ownership of the CameraProxy shared\n> between different componentes? Is it created or destoryed outside of\n> this class ? I would save all this complexity and store\n> V4L2CameraProxy instances in a vector, and point to them in the map at\n> openat() time.\n> \n> The only thing you care about is to increase the reference count to\n> the Camera, but you have a shared_ptr<> class instance in V4L2Camera,\n> so that's fine.\n\nTo support hot-unplug we'll likely need shared pointers, for now we can\ndo without them and use unique_ptr as explained above.\n\n> > +\n> > +\treturn newfd;\n> > +}\n> > +\n> > +int V4L2CompatManager::close(int fd)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > +\tif (proxy)\n> > +\t\treturn proxy->close();\n\nThis will break if the fd has been dup'ed. Closing one of the two fds\nwill close the proxy, the second fd will then be unusable. You need to\nrefcount opens in V4L2CameraProxy.\n\nYou also need to remove fd from the devices_ map here.\n\n> > +\n> > +\tint ret = close_func_(fd);\n> > +\treturn ret;\n\n\treturn close_func_(fd);\n\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> Align to open ( please\n\nit is aligned :-)\n\n> Mostly stylistic comments, so I think we're almost there, at least\n> from my side!\n> \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_[addr] = fd;\n\naddr is a hint, there's no guarantee that the buffer will be mapped at\naddr, and it is often set to NULL by the caller. The correct index for\nmmaps_ is map, not addr.\n\n> > +\treturn map;\n> > +}\n> > +\n> > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(addr);\n> > +\tif (!proxy)\n> > +\t\treturn munmap_func_(addr, length);\n\nAs getCamera(void *) is only called in this method, I would inline it:\n\n\tauto map = mmaps_.find(addr);\n\tif (map == mmaps_.end())\n\t\treturn munmap_func_(addr, length);\n\n\tV4L2CameraProxy *proxy = devices_.at(map->second).get();\n\n> > +\n> > +\tint ret = proxy->munmap(addr, length);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tmmaps_.erase(addr);\n\nAnd here you could then call\n\n\tmmaps_.erase(map);\n\n> > +\taddr = nullptr;\n\nThis isn't needed.\n\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..d5ae7810\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> > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > +#define __V4L2_COMPAT_MANAGER_H__\n> > +\n> > +#include <condition_variable>\n> > +#include <linux/videodev2.h>\n\nvideodev2.h isn't needed as far as I can tell.\n\n> > +#include <map>\n> > +#include <mutex>\n> > +#include <queue>\n\nI don't think you use std::queue here.\n\n> > +#include <sys/syscall.h>\n\nIs this needed here ?\n\n> > +#include <unistd.h>\n\nI don't think unistd.h is needed either, but you need fcntl.h for\nmode_t, and sys/types.h for off_t and size_t.\n\n> > +#include <vector>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/camera_manager.h>\n> > +#include <libcamera/stream.h>\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> > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > +\n> > +\tV4L2CameraProxy *getCamera(int fd);\n> > +\tV4L2CameraProxy *getCamera(void *addr);\n\nI would move those two methods either before openat() or after ioctl()\nin order to keep all the implementations of the C functions you\nintercept grouped together.\n\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> > +\n> > +\tstd::vector<std::shared_ptr<V4L2CameraProxy>> proxies_;\n> > +\tstd::map<int, std::shared_ptr<V4L2CameraProxy>> devices_;\n> > +\tstd::map<void *, int> 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[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A169360100\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Dec 2019 01:18:54 +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 CCF09A62;\n\tMon, 16 Dec 2019 01:18:53 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1576455534;\n\tbh=CwDkm9PhSPBIj8UVlIGY3eZ1i7Aa0YrJzhNJI3RocGg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=U7WAhFTCso3z9RlPLpJirtHqr0g9wHirXvfEI6L0MHSfhu2lIZSE167f3hQQ4a4SL\n\tz3fIRmD9vm5Zvg2bZWDTp4+/kS4KppOKFueWdKxmoMo5b1eMAZp9rGXCTpHQQB8Tai\n\tCDiju8f5CcZZrs4rOWWwU9xdj3eQxTYLDeQTXpbc=","Date":"Mon, 16 Dec 2019 02:18:44 +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":"<20191216001844.GG4889@pendragon.ideasonboard.com>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>\n\t<20191209103013.jw26qssnewhuumux@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20191209103013.jw26qssnewhuumux@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 16 Dec 2019 00:18:54 -0000"}},{"id":3262,"web_url":"https://patchwork.libcamera.org/comment/3262/","msgid":"<20191216044907.GA2689@emerald.amanokami.net>","date":"2019-12-16T04:49:07","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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 Jacopo,\n\nThanks for the review.\n\nOn Mon, Dec 09, 2019 at 11:30:13AM +0100, Jacopo Mondi wrote:\n> Hi Paul, thanks for the quick follow up\n> \n> On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> > Note that until we have a way of mapping V4L2 device nodes to libcamera\n> > cameras, the V4L2 compatibility layer will always selects and use the\n> > first enumerated libcamera camera.\n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> >\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             |  30 ++\n> >  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n> >  src/v4l2/v4l2_camera.h           |  67 +++++\n> >  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n> >  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n> >  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> >  10 files changed, 1469 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..1650048e\n> > --- /dev/null\n> > +++ b/src/v4l2/meson.build\n> > @@ -0,0 +1,30 @@\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> > +    '-fvisibility=hidden',\n> > +]\n> > +\n> > +v4l2_compat = shared_library('v4l2-compat',\n> > +                             v4l2_compat_sources,\n> > +                             name_prefix : '',\n> \n> Do you need this ? I tried removing it and it succesfully compiled...\n\nWithout it it'll become libv4l2-compat.so\n\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..f944c577\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -0,0 +1,299 @@\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> > +#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 \"v4l2_compat_manager.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +LOG_DECLARE_CATEGORY(V4L2Compat);\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> > +V4L2Camera::~V4L2Camera()\n> > +{\n> > +\twhile (!pendingRequests_.empty()) {\n> > +\t\tdelete pendingRequests_.front();\n> > +\t\tpendingRequests_.pop();\n> > +\t}\n> > +\n> > +\tbufferLock_.lock();\n> > +\twhile (!completedBuffers_.empty()) {\n> > +\t\tdelete completedBuffers_.front();\n> > +\t\tcompletedBuffers_.pop();\n> > +\t}\n> > +\tbufferLock_.unlock();\n> \n> Is locking required here ? The V4L2Camera is destroyed when the proxy\n> is destroyed, no other calls should be in-flight. Are all buffers\n> dequeued at this point ?\n\nI'm not sure. I put it in just in case. Is it ever possible that\nV4L2CompatManager will be destroyed after, say, a qbuf but before a\ncorresponding dqbuf? Like if the V4L2 application exits non-gracefully?\n\n> > +\n> > +\tcamera_->release();\n> > +}\n> > +\n> > +void V4L2Camera::open(int *ret, bool nonblock)\n> > +{\n> > +\tnonblock_ = nonblock;\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> \n> Probably roles should be handled differently, but I'm not sure how.\n> Does our API fall short here ? What if vewifinder is not supported,\n> should we be able to query what roles are supported from a camera ?\n\nI thought viewfinder was always supported...?\n\nThe only way to query is via V4L2... querycap? It doesn't look like we\ncan use it for roles?\n\n> > +\tif (config_ == nullptr) {\n> > +\t\t*ret = -EINVAL;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tupdateSizeImage();\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::updateSizeImage()\n> > +{\n> > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > +\tsizeimage_ =\n> > +\t\tV4L2CompatManager::imageSize(\n> \n> nit: this could fit on the previous line\n\nack\n\n> > +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> > +\t\t\tstreamConfig.size.width,\n> > +\t\t\tstreamConfig.size.height);\n> > +}\n> > +\n> > +void V4L2Camera::requestComplete(Request *request)\n> > +{\n> > +\tif (request->status() == Request::RequestCancelled) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Request not succesfully completed: \"\n> > +\t\t\t\t       << request->status();\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\t/* We only have one stream at the moment. */\n> > +\tbufferLock_.lock();\n> > +\tBuffer *buffer = request->buffers().begin()->second;\n> > +\tcompletedBuffers_.push(buffer);\n> > +\tbufferLock_.unlock();\n> > +\n> > +\tbufferSema_.release();\n> > +}\n> > +\n> > +void V4L2Camera::configure(int *ret, struct v4l2_format *arg,\n> > +\t\t\t   unsigned int bufferCount)\n> > +{\n> > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > +\tstreamConfig.size.width = arg->fmt.pix.width;\n> > +\tstreamConfig.size.height = arg->fmt.pix.height;\n> > +\tstreamConfig.pixelFormat =\n> > +\t\tV4L2CompatManager::v4l2ToDrm(arg->fmt.pix.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> > +\tupdateSizeImage();\n> > +\tif (sizeimage_ == 0)\n> \n> I'm always a bit scared by functions that updates a global variable as\n> side effect and let the caller responsability of making sure the\n> intended variable is set to the desired value.\n\nTrue...\n\n> I would rather make updateSizeImage() a calculateSizeImage(config) and\n> assign it to the the global sizeimage_ in the caller, or make it\n> return an error code.\n\nack\n\n> > +\t\t*ret = -EINVAL;\n> > +}\n> > +\n> > +void V4L2Camera::mmap(void **ret, int *err, void *addr, size_t length, int prot, off_t offset)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> > +\n> > +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> > +\t\t*ret = MAP_FAILED;\n> > +\t\t*err = ENOTSUP;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tunsigned int index = offset / sizeimage_;\n> > +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> > +\t\t*ret = MAP_FAILED;\n> > +\t\t*err = EINVAL;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tStream *stream = *camera_->streams().begin();\n> > +\t*ret = stream->buffers()[index].planes()[0].mem();\n> > +\t*err = 0;\n> > +}\n> > +\n> > +void V4L2Camera::munmap(int *ret, void *addr, size_t length)\n> > +{\n> > +\t*ret = 0;\n> > +\n> > +\tif (length != sizeimage_)\n> > +\t\t*ret = -EINVAL;\n> > +}\n> > +\n> > +void V4L2Camera::validStreamType(bool *ret, uint32_t type)\n> > +{\n> > +\t*ret = (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> > +}\n> > +\n> > +void V4L2Camera::validMemoryType(bool *ret, uint32_t memory)\n> > +{\n> > +\t*ret = (memory == V4L2_MEMORY_MMAP);\n> > +}\n> > +\n> > +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> \n> Empty line, to be consistent with other functions.\n> \n> But I actually wonder if we need this. i had the same in the android\n> HAL and it's a useful tracing tool, so I understand you might want to\n> keep them. After all, debug can be filtered out...\n\nI could remove it before pushing it. It's really useful to see if it's\nactually libcamera doing the work or if the V4L2 calls are going\ndirectly to V4L2.\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> > +\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> > +\twhile (!pendingRequests_.empty()) {\n> > +\t\t*ret = camera_->queueRequest(pendingRequests_.front());\n> > +\t\tpendingRequests_.pop();\n> > +\t\tif (*ret < 0)\n> > +\t\t\treturn;\n> > +\t}\n> \n> No error messages ? How does the error log looks like here ?\n\npendingRequests_ is a queue of V4L2 qbuf requests that were enqueued\nbefore V4L2 streamon, because in libcamera the order is opposite. So\nwhen the V4L2 -> libcamera streamon is called, then v4l2-compat enqueues\nthose V4L2 qbuf bufs that were queued before the streamon.\n\nCamera::queueRequest() returns 0 on success, so if they all succeed then\nthe final return value is 0. If any returns error, then we don't enqueue\nany further and return the error. The error message would just be the\none from Camera::queueRequest... which now that you mention it might\nlook weird coming from a VIDIOC_STREAMON... The caller would think that\nthe streamon failed even though it didn't, because what actually failed\nwas enqueueing the prequeued buffers...\n\nI'm not sure how best to resolve this... of the possible errors returned\nby Camera::queueRequest(), ENODEV, EACCES (which would be translated to\nEAGAIN), and ENOMEM are fine I think, but EINVAL would be really\nconfusing... :/\n\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, struct v4l2_buffer *arg)\n> > +{\n> > +\tStream *stream = config_->at(0).stream();\n> > +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(arg->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> > +\tRequest *request = camera_->createRequest();\n> > +\tif (request == nullptr) {\n> \n> Nit: could you unify on a single pattern ? My preference would be for\n>         if (!request)\n> \n> and I think the style guied suggests it as well\n\nack\n\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(request);\n> > +\t} else {\n> > +\t\t*ret = camera_->queueRequest(request);\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> > +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> > +\t*ret = 0;\n> > +}\n> > +\n> > +int V4L2Camera::dqbuf(struct v4l2_buffer *arg)\n> > +{\n> > +\tif (nonblock_ && !bufferSema_.tryAcquire())\n> > +\t\treturn -EAGAIN;\n> > +\telse\n> > +\t\tbufferSema_.acquire();\n> > +\n> > +\tbufferLock_.lock();\n> > +\tBuffer *buffer = completedBuffers_.front();\n> > +\tcompletedBuffers_.pop();\n> > +\tbufferLock_.unlock();\n> > +\n> > +\targ->bytesused = buffer->bytesused();\n> > +\targ->field = V4L2_FIELD_NONE;\n> > +\targ->timestamp.tv_sec = buffer->timestamp() / 1000000000;\n> > +\targ->timestamp.tv_usec = buffer->timestamp() / 1000;\n> > +\targ->sequence = buffer->sequence();\n> > +\n> > +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> \n> Shouldn't the FLAG_DONE bit be set ?\n\nUh yeah probably.\n\n> > +\n> > +\targ->length = sizeimage_;\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..73a427c4\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -0,0 +1,67 @@\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> > +#ifndef __V4L2_CAMERA_H__\n> > +#define __V4L2_CAMERA_H__\n> > +\n> > +#include <linux/videodev2.h>\n> > +#include <mutex>\n> > +#include <queue>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include \"semaphore.h\"\n> > +\n> > +using namespace libcamera;\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, bool nonblock);\n> > +\tvoid close(int *ret);\n> > +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> > +\tvoid requestComplete(Request *request);\n> > +\n> > +\tvoid mmap(void **ret, int *err, void *addr, size_t length,\n> > +\t\t  int prot, off_t offset);\n> > +\tvoid munmap(int *ret, void *addr, size_t length);\n> > +\n> > +\tvoid configure(int *ret, struct v4l2_format *arg,\n> > +\t\t       unsigned int bufferCount);\n> > +\n> > +\tvoid validStreamType(bool *ret, uint32_t type);\n> > +\tvoid validMemoryType(bool *ret, uint32_t memory);\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, struct v4l2_buffer *arg);\n> > +\tint dqbuf(struct v4l2_buffer *arg);\n> > +\n> > +private:\n> > +\tvoid updateSizeImage();\n> > +\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tstd::unique_ptr<CameraConfiguration> config_;\n> > +\n> > +\tunsigned int bufferCount_;\n> > +\tbool isRunning_;\n> > +\tbool nonblock_;\n> > +\n> > +\tunsigned int sizeimage_;\n> > +\n> > +\tSemaphore bufferSema_;\n> > +\tstd::mutex bufferLock_;\n> > +\n> > +\tstd::queue<Request *> pendingRequests_;\n> > +\tstd::queue<Buffer *> 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..66d558e4\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> > @@ -0,0 +1,452 @@\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> > +#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> > +V4L2CameraProxy::~V4L2CameraProxy()\n> > +{\n> > +}\n> \n> Do you need to declare and empty destructor ?\n\nI guess not.\n\n> > +\n> > +int V4L2CameraProxy::open(bool nonblock)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing OPEN\";\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> > +\t\t\t    &ret, nonblock);\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > +\tsetFmtFromConfig();\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::close()\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing CLOSE\";\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> > +\tvoid *val;\n> > +\tint err;\n> > +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> > +\t\t\t    &val, &err, addr, length, prot, offset);\n> > +\tif (val == MAP_FAILED)\n> > +\t\terrno = err;\n> > +\treturn val;\n> > +}\n> > +\n> > +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing MUNMAP\";\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::munmap, ConnectionTypeBlocking,\n> > +\t\t\t    &ret, addr, length);\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +bool V4L2CameraProxy::hasPixelFormat(unsigned int format)\n> > +{\n> > +\tconst std::vector<PixelFormat> &formats =\n> > +\t\tstreamConfig_.formats().pixelformats();\n> > +\treturn std::find(formats.begin(), formats.end(), format) != formats.end();\n> > +}\n> > +\n> > +/* \\todo getDeviceCaps? getMemoryCaps? */\n> > +\n> > +bool V4L2CameraProxy::hasSize(unsigned int format, Size size)\n> > +{\n> > +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> > +\treturn std::find(sizes.begin(), sizes.end(), size) != sizes.end();\n> > +}\n> > +\n> > +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> > +{\n> > +\tbool valid;\n> > +\tvcam_->invokeMethod(&V4L2Camera::validStreamType,\n> > +\t\t\t    ConnectionTypeBlocking, &valid, type);\n> > +\tif (!valid)\n> > +\t\treturn false;\n> > +\n> > +\treturn true;\n> \n> To answer you question on the reply to v1: if you prefer this stlye,\n> then it's fine with me. I would have\n>         return valid;\n> and that's it\n\nAh I see.\n\n> > +}\n> > +\n> > +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> > +{\n> > +\tbool valid;\n> > +\tvcam_->invokeMethod(&V4L2Camera::validMemoryType,\n> > +\t\t\t    ConnectionTypeBlocking, &valid, memory);\n> > +\tif (!valid)\n> > +\t\treturn false;\n> > +\n> > +\treturn true;\n> > +}\n> > +\n> > +void V4L2CameraProxy::setFmtFromConfig()\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> > +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> > +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> \n> This should come from some library wide header I guess. Having it\n> hard-coded here won't scale. Not for this series probably, but is\n> worth a todo I guess\n\nI'll put it as a todo.\n\n> > +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> > +\tcapabilities_.capabilities =\n> > +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\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\treturn -EINVAL;\n> > +\tif (arg->index > streamConfig_.formats().pixelformats().size())\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tmemcpy(arg->description, \"asdf\", 5);\n> \n> If we don't want to introduce the format->description map now, add a\n> todo please\n\nack\n\n> > +\targ->pixelformat =\n> > +\t\tV4L2CompatManager::drmToV4L2(\n> \n> Nit: fits on the previous line\n\nack\n\n> > +\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> > +\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,\n> > +\t\t\t    &ret, arg, bufferCount_);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > +\tsetFmtFromConfig();\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> > +\tif (!hasPixelFormat(format))\n> > +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> > +\n> > +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> > +\tif (!hasSize(format, size))\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> \n> Nit: hard to parse\n\nI used this weird indentation to show what is being grouped. So the one\nthat's indented is inside the parens of the parent indent, and the\narg->fmt.pix.width is outside of the parens, and is one of the arguments\nto the multiplication operator.\n\n> \targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n> \t\t\t                V4L2CompatManager::drmToV4L2(format)) *\n>                                         arg->fmt.pix.width;\n> \n>  ?\n> \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> > +\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\treturn -EINVAL;\n> > +\tif (!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,\n> > +\t\t\t    &ret, &curV4L2Format_, arg->count);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\targ->count = streamConfig_.bufferCount;\n> > +\tbufferCount_ = arg->count;\n> > +\n> > +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> > +\t\treturn -EINVAL;\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\treturn -EINVAL;\n> > +\tif (arg->index >= stream->buffers().size())\n> > +\t\treturn -EINVAL;\n> \n> Nit: could be made a single condition, up to you\n\nHmmmm I suppose...\n\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> \n> So, the validation of the buffer and memory type is performed by\n> invoking a method on the V4L2Camera, while we here hardcode them in\n> the proxy... Not big, but I would rather keep the information here or\n> there (I would drop the method call for the validation, as so far we\n> only support one type)\n\nI was thinking the same thing...\n\nI added the validation calls so we could scale (?) to multiple types\nlater on, if we decide to... That's why I had the DMABUF in there too\nuntil I got rid of it last/this time.\n\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\treturn -EINVAL;\n> > +\tif (!validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\tif (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);\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\treturn -EINVAL;\n> > +\tif (!validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\targ->index = currentBuf_;\n> > +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> > +\n> > +\treturn vcam_->dqbuf(arg);\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> > +\tcase VIDIOC_EXPBUF:\n> > +\tcase VIDIOC_ENUM_FRAMESIZES:\n> \n> Aren't there more ioctl calls ? Why are these two special ? Can't they\n> be catched by default like the others ?\n\nThey're not special :)\n\n> > +\tdefault:\n> > +\t\tret = -ENOTTY;\n> > +\t}\n> > +\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\terrno = 0;\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..64c7aadd\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.h\n> > @@ -0,0 +1,63 @@\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> > +#ifndef __V4L2_CAMERA_PROXY_H__\n> > +#define __V4L2_CAMERA_PROXY_H__\n> > +\n> > +#include <linux/videodev2.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> > +\t~V4L2CameraProxy();\n> > +\n> > +\tint open(bool nonblock);\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> > +private:\n> > +\tbool hasPixelFormat(unsigned int format);\n> > +\tbool hasSize(unsigned int format, Size size);\n> > +\tbool validateStreamType(uint32_t type);\n> > +\tbool validateMemoryType(uint32_t memory);\n> > +\tvoid setFmtFromConfig();\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 index_;\n> > +\n> > +\tstruct v4l2_format curV4L2Format_;\n> > +\tStreamConfiguration streamConfig_;\n> > +\tstruct v4l2_capability capabilities_;\n> > +\tunsigned int bufferCount_;\n> > +\tunsigned int currentBuf_;\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..3330e7bc\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat.cpp\n> > @@ -0,0 +1,81 @@\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 <iostream>\n> > +\n> > +#include <errno.h>\n> > +#include <fcntl.h>\n> > +#include <linux/videodev2.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> > +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 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> > +\tvoid *val = V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> > +\treturn val;\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..90416b35\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > @@ -0,0 +1,382 @@\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> > +#include \"v4l2_compat_manager.h\"\n> > +\n> > +#include <dlfcn.h>\n> > +#include <fcntl.h>\n> > +#include <fstream>\n> > +#include <iostream>\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)\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> > +\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);\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\n> > +\t * gets created here to wraps 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\n> > +\t * as we're now ready to handle calls from the framework.\n> > +\t */\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 v4l2CompatManager;\n> > +\treturn &v4l2CompatManager;\n> > +}\n> > +\n> > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> > +{\n> > +\tauto device = devices_.find(fd);\n> > +\tif (device == devices_.end())\n> > +\t\treturn nullptr;\n> > +\n> > +\treturn device->second.get();\n> > +}\n> > +\n> > +V4L2CameraProxy *V4L2CompatManager::getCamera(void *addr)\n> > +{\n> > +\tauto map = mmaps_.find(addr);\n> > +\tif (map == mmaps_.end())\n> > +\t\treturn nullptr;\n> > +\n> > +\treturn devices_.at(map->second).get();\n> > +}\n> > +\n> > +int V4L2CompatManager::getCameraIndex(int fd)\n> > +{\n> > +\tstruct stat statbuf;\n> > +\tfstat(fd, &statbuf);\n> > +\tunsigned int dev_major = major(statbuf.st_rdev);\n> > +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n> > +\n> > +\tstd::string name;\n> > +\tstd::ifstream nameFile;\n> > +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> > +\t\t      std::to_string(dev_minor) + \"/name\");\n> > +\tif (!nameFile)\n> > +\t\treturn -1;\n> > +\n> > +\tstd::getline(nameFile, name);\n> > +\tnameFile.close();\n> > +\n> > +\tunsigned int index = 0;\n> > +\tfor (auto &camera : cm_->cameras()) {\n> > +\t\tif (!camera->name().compare(name))\n> > +\t\t\tbreak;\n> > +\t\t++index;\n> > +\t}\n> > +\n> > +\tif (index >= cm_->cameras().size())\n> > +\t\treturn -1;\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> > +\tif (std::string(path).find(\"video\") == std::string::npos)\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, Error) << \"No camera found for \" << path;\n> > +\t\treturn fd;\n> > +\t}\n> > +\n> > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > +\n> > +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> \n> You here share the ownership of the managed object owned by\n> proxies_[camera_index] with a local variable, increasing its\n> reference count\n> \n> > +\tret = proxy->open(mode & O_NONBLOCK);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> > +\tif (efd < 0)\n> > +\t\treturn efd;\n> \n> I'm still not convinced about this, I'll go read your reply to v1\n> again.\n> \n> > +\n> > +\tdevices_.emplace(efd, proxy);\n> \n> And here you store it permanently in a map, making its reference count\n> permanently = 2 (it goes up to 3, then the local variable goes out of\n> scope and gest back to 2).\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_[newfd] = devices_[oldfd];\n> \n> Then here you create a new copy, with reference count = 3\n> \n> At delete time you clear all the entries in devices_, causing the\n> shared pointer stored in proxies_ to get back to 1, then proxies_ goes\n> out of scope, and the managed object gets deleted.\n> \n> Why all this dance ? Is the ownership of the CameraProxy shared\n> between different componentes? Is it created or destoryed outside of\n> this class ? I would save all this complexity and store\n> V4L2CameraProxy instances in a vector, and point to them in the map at\n> openat() time.\n> \n> The only thing you care about is to increase the reference count to\n> the Camera, but you have a shared_ptr<> class instance in V4L2Camera,\n> so that's fine.\n\nHmm... I'm going to go with Laurent's suggestion...\n\ntbh I'm not sure why I used shared pointers...\n\n> > +\n> > +\treturn newfd;\n> > +}\n> > +\n> > +int V4L2CompatManager::close(int fd)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > +\tif (proxy)\n> > +\t\treturn proxy->close();\n> > +\n> > +\tint ret = close_func_(fd);\n> > +\treturn ret;\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> Align to open ( please\n\nIt is aligned :)\n\n> Mostly stylistic comments, so I think we're almost there, at least\n> from my side!\n\nThanks!\n\n\nPaul\n\n> \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_[addr] = fd;\n> > +\treturn map;\n> > +}\n> > +\n> > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > +{\n> > +\tV4L2CameraProxy *proxy = getCamera(addr);\n> > +\tif (!proxy)\n> > +\t\treturn munmap_func_(addr, length);\n> > +\n> > +\tint ret = proxy->munmap(addr, length);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tmmaps_.erase(addr);\n> > +\taddr = nullptr;\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..d5ae7810\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> > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > +#define __V4L2_COMPAT_MANAGER_H__\n> > +\n> > +#include <condition_variable>\n> > +#include <linux/videodev2.h>\n> > +#include <map>\n> > +#include <mutex>\n> > +#include <queue>\n> > +#include <sys/syscall.h>\n> > +#include <unistd.h>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/camera_manager.h>\n> > +#include <libcamera/stream.h>\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> > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > +\n> > +\tV4L2CameraProxy *getCamera(int fd);\n> > +\tV4L2CameraProxy *getCamera(void *addr);\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> > +\n> > +\tstd::vector<std::shared_ptr<V4L2CameraProxy>> proxies_;\n> > +\tstd::map<int, std::shared_ptr<V4L2CameraProxy>> devices_;\n> > +\tstd::map<void *, int> 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":"<paul.elder@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AC51660475\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Dec 2019 05:49:13 +0100 (CET)","from emerald.amanokami.net\n\t(50-254-7-5-static.hfc.comcastbusiness.net [50.254.7.5])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 887C0DD;\n\tMon, 16 Dec 2019 05:49:12 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1576471753;\n\tbh=TnDDfAi1ae9rLVs+9NKv0+DjUhX3fXGxTacjga5eJ4g=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=SOcbVneAZS2jaoVY/U7Pz2bKVPj86Jr4t27czDcxpkdQMVXWWIYm/nl1TX8xNQK3i\n\tBV3n4o6T9VTM56qtvEuLOfCdIX3BXWkRTkrVA1fTIZl2ONcpevyjljLjfJiQJOQ8X+\n\tx+oPCHdAXz3kSO6mM/hkJYHWHujq09V2utaZBg2Q=","Date":"Sun, 15 Dec 2019 22:49:07 -0600","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20191216044907.GA2689@emerald.amanokami.net>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>\n\t<20191209103013.jw26qssnewhuumux@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20191209103013.jw26qssnewhuumux@uno.localdomain>","User-Agent":"Mutt/1.9.4 (2018-02-28)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 16 Dec 2019 04:49:14 -0000"}},{"id":3263,"web_url":"https://patchwork.libcamera.org/comment/3263/","msgid":"<20191216061853.GA5801@emerald.amanokami.net>","date":"2019-12-16T06:18:53","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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 Mon, Dec 16, 2019 at 02:18:44AM +0200, Laurent Pinchart wrote:\n> Hi Paul,\n> \n> Thank you for the patch.\n> \n> On Mon, Dec 09, 2019 at 11:30:13AM +0100, Jacopo Mondi wrote:\n> > On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> > > Note that until we have a way of mapping V4L2 device nodes to libcamera\n> > > cameras, the V4L2 compatibility layer will always selects and use the\n> \n> s/selects/select/\n\nThis paragraph got removed after I replaced it with the short paragraph\ndescribing the temporary (?) mapping using sysfs names.\n\n> > > first enumerated libcamera camera.\n> > >\n> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > >\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             |  30 ++\n> > >  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n> > >  src/v4l2/v4l2_camera.h           |  67 +++++\n> > >  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n> > >  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n> > >  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n> > >  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n> > >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> > >  10 files changed, 1469 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> How about 'Compile the V4L2 adaptation layer', as it's not part of\n> libcamera.so ?\n\nack\n\nAlthough, I wrote \"compatibility\" instead of \"adaptation\" everywhere\nbecause that's what the documentation on libcamera.org says (V4L2\nCompatibility Layer).\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..1650048e\n> > > --- /dev/null\n> > > +++ b/src/v4l2/meson.build\n> > > @@ -0,0 +1,30 @@\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> \n> As explained in my review of v1, I would prefer also defining\n> _FILE_OFFSET_BITS explicitly.\n\nack\n\n> > > +    '-fvisibility=hidden',\n> > > +]\n> > > +\n> > > +v4l2_compat = shared_library('v4l2-compat',\n> > > +                             v4l2_compat_sources,\n> > > +                             name_prefix : '',\n> > \n> > Do you need this ? I tried removing it and it succesfully compiled...\n> \n> But does it then generate a shared object without the 'lib' prefix ?\n> \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> \n> [snip]\n> \n> Skipping over V4L2Camera and V4L2CameraProxy, I'll get to those\n> tomorrow.\n> \n> > > diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp\n> > > new file mode 100644\n> > > index 00000000..3330e7bc\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_compat.cpp\n> > > @@ -0,0 +1,81 @@\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> \"V4L2 adaptation layer\" ?\n\nThe documentation page on libcamera.org says V4L2 Compatibility Layer :)\n\n> > > + */\n> > > +\n> > > +#include \"v4l2_compat_manager.h\"\n> > > +\n> > > +#include <iostream>\n> \n> You don't need this header.\n\nack\n\n> > > +\n> > > +#include <errno.h>\n> > > +#include <fcntl.h>\n> > > +#include <linux/videodev2.h>\n> \n> I don't think you need this header either.\n\nack\n\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> I would add a blank line here, as you add a blank line before the\n> corresponding }.\n\nack\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 openat(AT_FDCWD, path, oflag, mode);\n> \n> You could call\n> \n> \treturn V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag,\n> \t\t\t\t\t\t     mode);\n> \n> to avoid one layer of indirection.\n\nack\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> > > +\tvoid *val = V4L2CompatManager::instance()->mmap(addr, length, prot, flags, fd, offset);\n> > > +\treturn val;\n> \n> Anything wrong with\n> \n> \treturn V4L2CompatManager::instance()->mmap(addr, length, prot, flags,\n> \t\t\t\t\t\t   fd, offset);\n> \n> ?\n\nack\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..90416b35\n> > > --- /dev/null\n> > > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > > @@ -0,0 +1,382 @@\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> Missing blank line.\n\nack\n\n...wait most of my files are like this; gotta fix 'em all\n\n> > > +#include \"v4l2_compat_manager.h\"\n> > > +\n> > > +#include <dlfcn.h>\n> > > +#include <fcntl.h>\n> > > +#include <fstream>\n> > > +#include <iostream>\n> \n> I don't think you need iostream here either.\n\nack\n\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)\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> You can remove one space before the ) in each of those lines.\n\nack\n\n> > > +}\n> > > +\n> > > +V4L2CompatManager::~V4L2CompatManager()\n> > > +{\n> > > +\tdevices_.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);\n> \n> There's a risk of a race condition here, where the new thread could\n> notify the condition variable before you start waiting on it. The wait\n> would then never complete. You need to add an initialized_ member to the\n> class, initialize it to false in the constructor, set it to true before\n> calling cv_.notify_one() in run(), and call\n> \n> \tcv_.wait(locker, []{ return initialized_; });\n> \n> here.\n\nack\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\n> > > +\t * gets created here to wraps a camera device.\n> \n> s/wraps/wrap/\n\nThis was copied from CameraHalManager::run(), which means that that has\nthe same typo :)\n\n> and you can go up to 80 columns before wrapping.\n\nack\n\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\n> > > +\t * as we're now ready to handle calls from the framework.\n> \n> You can go up to 80 columns before wrapping here too.\n\nack\n\n> > > +\t */\n> \n> \tmutex_.lock();\n> \tinitialized_ = true;\n> \tmutex_.unlock();\n\nack\n\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 v4l2CompatManager;\n> > > +\treturn &v4l2CompatManager;\n> \n> s/v4l2CompatManager/instance/ ?\n\nack\n\n> > > +}\n> > > +\n> > > +V4L2CameraProxy *V4L2CompatManager::getCamera(int fd)\n> > > +{\n> > > +\tauto device = devices_.find(fd);\n> > > +\tif (device == devices_.end())\n> > > +\t\treturn nullptr;\n> > > +\n> > > +\treturn device->second.get();\n> > > +}\n> > > +\n> > > +V4L2CameraProxy *V4L2CompatManager::getCamera(void *addr)\n> > > +{\n> > > +\tauto map = mmaps_.find(addr);\n> > > +\tif (map == mmaps_.end())\n> > > +\t\treturn nullptr;\n> > > +\n> > > +\treturn devices_.at(map->second).get();\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::getCameraIndex(int fd)\n> > > +{\n> > > +\tstruct stat statbuf;\n> > > +\tfstat(fd, &statbuf);\n> > > +\tunsigned int dev_major = major(statbuf.st_rdev);\n> > > +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n> \n> devMajor and devMinor.\n\nack\n\n> > > +\n> > > +\tstd::string name;\n> > > +\tstd::ifstream nameFile;\n> > > +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> > > +\t\t      std::to_string(dev_minor) + \"/name\");\n> > > +\tif (!nameFile)\n> > > +\t\treturn -1;\n> > > +\n> > > +\tstd::getline(nameFile, name);\n> > > +\tnameFile.close();\n> > > +\n> > > +\tunsigned int index = 0;\n> > > +\tfor (auto &camera : cm_->cameras()) {\n> > > +\t\tif (!camera->name().compare(name))\n> > > +\t\t\tbreak;\n> > > +\t\t++index;\n> > > +\t}\n> \n> As commented on v1, matching on the video node name isn't a good idea,\n> as we want to support all cameras, and not all cameras are named based\n> on the video node name. I wrote\n> \n> \"One option would be to add a method to the camera manager to retrieve a\n> camera by device node path. This wouldn't expose any V4L2-specific\n> information towards applications, the map of device nodes to cameras\n> would be private.\"\n> \n> but I don't think that's right, we should instead retrieve a camera by\n> major:minor, as multiple device nodes in the system could \n\nDoesn't the major:minor devnum map uniquely to device node paths?\n\nWhat do we do when different device nodes/devnums map to the same\nlibcamera camera but different stream etc? Simply map to the libcamera\ncamera at the simplest setting?\n\n> > > +\n> > > +\tif (index >= cm_->cameras().size())\n> > > +\t\treturn -1;\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> > > +\tif (std::string(path).find(\"video\") == std::string::npos)\n> > > +\t\treturn fd;\n> \n> So opening \"/home/user/myvideo.mp4\" will match :-)\n\nOh, oops :p\n\n> You can't match by name here, you need to fstat() and check that the\n> file is a character device node whose major is equal to the V4L2 major.\n\nI thought I did that a long time ago... I guess it disappeared.\n\n> Or, if you add a method to the camera manager to retrieve a camera by\n> major:minor, I think you could even skip this check completely as it\n> would be handled in CameraManager (but the fstat is needed nonetheless).\n\nack\n\n> > > +\n> > > +\tif (!isRunning())\n> > > +\t\tinit();\n> > > +\n> > > +\tint ret = getCameraIndex(fd);\n> > > +\tif (ret < 0) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"No camera found for \" << path;\n> \n> I'd turn that into an Info or Debug message as it's not necessarily an\n> error, not all cameras are supported by libcamera.\n\nTrue.\n\n> > > +\t\treturn fd;\n> > > +\t}\n> \n> You need to close_func_(fd) here.\n\nWhy? The application has called open() on a V4L2 device node that we\ndon't support, so shouldn't we just let it through? Should we block it\ninstead?\n\n> > > +\n> > > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > > +\n> > > +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> > \n> > You here share the ownership of the managed object owned by\n> > proxies_[camera_index] with a local variable, increasing its\n> > reference count\n> > \n> > > +\tret = proxy->open(mode & O_NONBLOCK);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> > > +\tif (efd < 0)\n> > > +\t\treturn efd;\n> > \n> > I'm still not convinced about this, I'll go read your reply to v1\n> > again.\n> \n> What's wrong with it ? We need an fd that applications can use in a\n> select() or poll() call, so we can't return the fd for the video node,\n> we need an eventfd that we can signal when a request completes.\n> \n> > > +\n> > > +\tdevices_.emplace(efd, proxy);\n> > \n> > And here you store it permanently in a map, making its reference count\n> > permanently = 2 (it goes up to 3, then the local variable goes out of\n> > scope and gest back to 2).\n> \n> I don't think this is a big issue, and reference counting is needed to\n> support hot-unplug. As we don't support it yet we could turn proxies_\n> into a std::vector<std::unique_ptr<V4L2CameraProxy>> and devices_ into a\n> std::map<int, V4L2CameraProxy *>>, knowing that the device won't be\n> deleted until the V4L2CompatManager itself is deleted. The above lookup\n> in proxies_ would then become\n> \n> \tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> \n> I think the mmaps_ could then map from void * to V4L2CameraProxy *,\n> which would avoid double look-ups in munmap().\n\nGood idea.\n\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_[newfd] = devices_[oldfd];\n> > \n> > Then here you create a new copy, with reference count = 3\n> > \n> > At delete time you clear all the entries in devices_, causing the\n> > shared pointer stored in proxies_ to get back to 1, then proxies_ goes\n> > out of scope, and the managed object gets deleted.\n> > \n> > Why all this dance ? Is the ownership of the CameraProxy shared\n> > between different componentes? Is it created or destoryed outside of\n> > this class ? I would save all this complexity and store\n> > V4L2CameraProxy instances in a vector, and point to them in the map at\n> > openat() time.\n> > \n> > The only thing you care about is to increase the reference count to\n> > the Camera, but you have a shared_ptr<> class instance in V4L2Camera,\n> > so that's fine.\n> \n> To support hot-unplug we'll likely need shared pointers, for now we can\n> do without them and use unique_ptr as explained above.\n> \n> > > +\n> > > +\treturn newfd;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::close(int fd)\n> > > +{\n> > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > +\tif (proxy)\n> > > +\t\treturn proxy->close();\n> \n> This will break if the fd has been dup'ed. Closing one of the two fds\n> will close the proxy, the second fd will then be unusable. You need to\n> refcount opens in V4L2CameraProxy.\n\nOh yeah...\n\n> You also need to remove fd from the devices_ map here.\n> \n> > > +\n> > > +\tint ret = close_func_(fd);\n> > > +\treturn ret;\n> \n> \treturn close_func_(fd);\n> \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> > Align to open ( please\n> \n> it is aligned :-)\n> \n> > Mostly stylistic comments, so I think we're almost there, at least\n> > from my side!\n> > \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_[addr] = fd;\n> \n> addr is a hint, there's no guarantee that the buffer will be mapped at\n> addr, and it is often set to NULL by the caller. The correct index for\n> mmaps_ is map, not addr.\n\nack\n\n> > > +\treturn map;\n> > > +}\n> > > +\n> > > +int V4L2CompatManager::munmap(void *addr, size_t length)\n> > > +{\n> > > +\tV4L2CameraProxy *proxy = getCamera(addr);\n> > > +\tif (!proxy)\n> > > +\t\treturn munmap_func_(addr, length);\n> \n> As getCamera(void *) is only called in this method, I would inline it:\n> \n> \tauto map = mmaps_.find(addr);\n> \tif (map == mmaps_.end())\n> \t\treturn munmap_func_(addr, length);\n> \n> \tV4L2CameraProxy *proxy = devices_.at(map->second).get();\n> \n> > > +\n> > > +\tint ret = proxy->munmap(addr, length);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tmmaps_.erase(addr);\n> \n> And here you could then call\n> \n> \tmmaps_.erase(map);\n> \n> > > +\taddr = nullptr;\n> \n> This isn't needed.\n\nack\n\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..d5ae7810\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> > > +#ifndef __V4L2_COMPAT_MANAGER_H__\n> > > +#define __V4L2_COMPAT_MANAGER_H__\n> > > +\n> > > +#include <condition_variable>\n> > > +#include <linux/videodev2.h>\n> \n> videodev2.h isn't needed as far as I can tell.\n> \n> > > +#include <map>\n> > > +#include <mutex>\n> > > +#include <queue>\n> \n> I don't think you use std::queue here.\n> \n> > > +#include <sys/syscall.h>\n> \n> Is this needed here ?\n> \n> > > +#include <unistd.h>\n> \n> I don't think unistd.h is needed either, but you need fcntl.h for\n> mode_t, and sys/types.h for off_t and size_t.\n\nack\n\n> > > +#include <vector>\n> > > +\n> > > +#include <libcamera/camera.h>\n> > > +#include <libcamera/camera_manager.h>\n> > > +#include <libcamera/stream.h>\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> > > +\tint openat(int dirfd, const char *path, int oflag, mode_t mode);\n> > > +\n> > > +\tV4L2CameraProxy *getCamera(int fd);\n> > > +\tV4L2CameraProxy *getCamera(void *addr);\n> \n> I would move those two methods either before openat() or after ioctl()\n> in order to keep all the implementations of the C functions you\n> intercept grouped together.\n\nack\n\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> > > +\n> > > +\tstd::vector<std::shared_ptr<V4L2CameraProxy>> proxies_;\n> > > +\tstd::map<int, std::shared_ptr<V4L2CameraProxy>> devices_;\n> > > +\tstd::map<void *, int> mmaps_;\n> > > +};\n> > > +\n> > > +#endif /* __V4L2_COMPAT_MANAGER_H__ */\n\n\nThanks,\n\nPaul","headers":{"Return-Path":"<paul.elder@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 91FDD60475\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Dec 2019 07:18:59 +0100 (CET)","from emerald.amanokami.net\n\t(50-254-7-5-static.hfc.comcastbusiness.net [50.254.7.5])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 53B3FDD;\n\tMon, 16 Dec 2019 07:18:58 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1576477139;\n\tbh=GQI3vvYJxnRNI5oCKO4lh7FNj503FkqkteYgK39+b64=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=GgyyD6iweg3Zrh0S8Lc0zEraNQ+BLXSq8XbjTnBGXuo9iCxaA4zDKvQV8zOK344UC\n\tnhA3WvOB8by0c67lgUyruL6+iDr71MrEynAbNsnrqwVvrnB3GGZrGCLGNB8XYX8Ost\n\tbLmpqN5kuRe3Ea04YmJusTdy5IDMs+MDDhrAqJIQ=","Date":"Mon, 16 Dec 2019 00:18:53 -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":"<20191216061853.GA5801@emerald.amanokami.net>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>\n\t<20191209103013.jw26qssnewhuumux@uno.localdomain>\n\t<20191216001844.GG4889@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20191216001844.GG4889@pendragon.ideasonboard.com>","User-Agent":"Mutt/1.9.4 (2018-02-28)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 16 Dec 2019 06:18:59 -0000"}},{"id":3264,"web_url":"https://patchwork.libcamera.org/comment/3264/","msgid":"<20191216120728.GA4856@pendragon.ideasonboard.com>","date":"2019-12-16T12:07:28","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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 16, 2019 at 12:18:53AM -0600, Paul Elder wrote:\n> On Mon, Dec 16, 2019 at 02:18:44AM +0200, Laurent Pinchart wrote:\n> > On Mon, Dec 09, 2019 at 11:30:13AM +0100, Jacopo Mondi wrote:\n> > > On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> > > > Note that until we have a way of mapping V4L2 device nodes to libcamera\n> > > > cameras, the V4L2 compatibility layer will always selects and use the\n> > \n> > s/selects/select/\n> \n> This paragraph got removed after I replaced it with the short paragraph\n> describing the temporary (?) mapping using sysfs names.\n> \n> > > > first enumerated libcamera camera.\n> > > >\n> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > > >\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             |  30 ++\n> > > >  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n> > > >  src/v4l2/v4l2_camera.h           |  67 +++++\n> > > >  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n> > > >  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n> > > >  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n> > > >  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n> > > >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> > > >  10 files changed, 1469 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> > How about 'Compile the V4L2 adaptation layer', as it's not part of\n> > libcamera.so ?\n> \n> ack\n> \n> Although, I wrote \"compatibility\" instead of \"adaptation\" everywhere\n> because that's what the documentation on libcamera.org says (V4L2\n> Compatibility Layer).\n\nThen please disregard my comment. I misremembered that we called the\ncomponent V4L2 adaptation everywhere, but I was wrong.\n\n[snip]\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..90416b35\n> > > > --- /dev/null\n> > > > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > > > @@ -0,0 +1,382 @@\n\n[snip]\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\n> > > > +\t * gets created here to wraps a camera device.\n> > \n> > s/wraps/wrap/\n> \n> This was copied from CameraHalManager::run(), which means that that has\n> the same typo :)\n\nPlease send a patch to fix that too :-)\n\n> > and you can go up to 80 columns before wrapping.\n> \n> ack\n> \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\n> > > > +\t * as we're now ready to handle calls from the framework.\n> > \n> > You can go up to 80 columns before wrapping here too.\n> \n> ack\n> \n> > > > +\t */\n> > \n> > \tmutex_.lock();\n> > \tinitialized_ = true;\n> > \tmutex_.unlock();\n> \n> ack\n> \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[snip]\n\n> > > > +int V4L2CompatManager::getCameraIndex(int fd)\n> > > > +{\n> > > > +\tstruct stat statbuf;\n> > > > +\tfstat(fd, &statbuf);\n> > > > +\tunsigned int dev_major = major(statbuf.st_rdev);\n> > > > +\tunsigned int dev_minor = minor(statbuf.st_rdev);\n> > \n> > devMajor and devMinor.\n> \n> ack\n> \n> > > > +\n> > > > +\tstd::string name;\n> > > > +\tstd::ifstream nameFile;\n> > > > +\tnameFile.open(\"/sys/dev/char/\" + std::to_string(dev_major) + \":\" +\n> > > > +\t\t      std::to_string(dev_minor) + \"/name\");\n> > > > +\tif (!nameFile)\n> > > > +\t\treturn -1;\n> > > > +\n> > > > +\tstd::getline(nameFile, name);\n> > > > +\tnameFile.close();\n> > > > +\n> > > > +\tunsigned int index = 0;\n> > > > +\tfor (auto &camera : cm_->cameras()) {\n> > > > +\t\tif (!camera->name().compare(name))\n> > > > +\t\t\tbreak;\n> > > > +\t\t++index;\n> > > > +\t}\n> > \n> > As commented on v1, matching on the video node name isn't a good idea,\n> > as we want to support all cameras, and not all cameras are named based\n> > on the video node name. I wrote\n> > \n> > \"One option would be to add a method to the camera manager to retrieve a\n> > camera by device node path. This wouldn't expose any V4L2-specific\n> > information towards applications, the map of device nodes to cameras\n> > would be private.\"\n> > \n> > but I don't think that's right, we should instead retrieve a camera by\n> > major:minor, as multiple device nodes in the system could \n> \n> Doesn't the major:minor devnum map uniquely to device node paths?\n\n# mknod /dev/video0 c 81 0\n# mknod /dev/video0-alias c 81 0\n\nand you have two device nodes for the same devnums :-) Without going to\nthose extremes, I have on my system\n\n$ ls -l /dev/video*\ncrw-rw----+ 1 root video 81, 0 Dec 16 13:19 /dev/video0\ncrw-rw----+ 1 root video 81, 1 Dec 16 13:19 /dev/video1\ncrw-rw----+ 1 root video 81, 2 Dec 16 13:19 /dev/video2\ncrw-rw----+ 1 root video 81, 3 Dec 16 13:19 /dev/video3\n$ ls -l /dev/v4l/by-id/\ntotal 0\nlrwxrwxrwx 1 root root 12 Dec 16 13:19 usb-CN0FFMHCLOG008AAB2XTA01_Integrated_Webcam_HD_200901010001-video-index0 -> ../../video0\nlrwxrwxrwx 1 root root 12 Dec 16 13:19 usb-CN0FFMHCLOG008AAB2XTA01_Integrated_Webcam_HD_200901010001-video-index1 -> ../../video3\n\nAnd finally, open(\"/dev/video0\") and open(\"video0\") with the current\ndirectory being \"/dev\" (or the equivalent with openat()) both map to the\nsame file, but pass a different path to openat(). You would at the very\nleast need to determine the canonical path byt dereferencing symlinks\nand making the path absolute, which would be more complicated than\nlooking up based on devnum.\n\n> What do we do when different device nodes/devnums map to the same\n> libcamera camera but different stream etc? Simply map to the libcamera\n> camera at the simplest setting?\n\nTo start with, we can limit the support to a single stream per camera.\nLet's have pipelines report to the CameraManager one devnum per Camera,\nand then offer a method to get a camera by devnum. That should be enough\nto move forward.\n\n> > > > +\n> > > > +\tif (index >= cm_->cameras().size())\n> > > > +\t\treturn -1;\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> > > > +\tif (std::string(path).find(\"video\") == std::string::npos)\n> > > > +\t\treturn fd;\n> > \n> > So opening \"/home/user/myvideo.mp4\" will match :-)\n> \n> Oh, oops :p\n> \n> > You can't match by name here, you need to fstat() and check that the\n> > file is a character device node whose major is equal to the V4L2 major.\n> \n> I thought I did that a long time ago... I guess it disappeared.\n> \n> > Or, if you add a method to the camera manager to retrieve a camera by\n> > major:minor, I think you could even skip this check completely as it\n> > would be handled in CameraManager (but the fstat is needed nonetheless).\n> \n> ack\n> \n> > > > +\n> > > > +\tif (!isRunning())\n> > > > +\t\tinit();\n> > > > +\n> > > > +\tint ret = getCameraIndex(fd);\n> > > > +\tif (ret < 0) {\n> > > > +\t\tLOG(V4L2Compat, Error) << \"No camera found for \" << path;\n> > \n> > I'd turn that into an Info or Debug message as it's not necessarily an\n> > error, not all cameras are supported by libcamera.\n> \n> True.\n> \n> > > > +\t\treturn fd;\n> > > > +\t}\n> > \n> > You need to close_func_(fd) here.\n> \n> Why? The application has called open() on a V4L2 device node that we\n> don't support, so shouldn't we just let it through? Should we block it\n> instead?\n\nI meant after the }, sorry for not being clear. Otherwise you leak fd\nfor devices that map to a camera.\n\n> > > > +\n> > > > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > > > +\n> > > > +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> > > \n> > > You here share the ownership of the managed object owned by\n> > > proxies_[camera_index] with a local variable, increasing its\n> > > reference count\n> > > \n> > > > +\tret = proxy->open(mode & O_NONBLOCK);\n> > > > +\tif (ret < 0)\n> > > > +\t\treturn ret;\n> > > > +\n> > > > +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> > > > +\tif (efd < 0)\n> > > > +\t\treturn efd;\n> > > \n> > > I'm still not convinced about this, I'll go read your reply to v1\n> > > again.\n> > \n> > What's wrong with it ? We need an fd that applications can use in a\n> > select() or poll() call, so we can't return the fd for the video node,\n> > we need an eventfd that we can signal when a request completes.\n> > \n> > > > +\n> > > > +\tdevices_.emplace(efd, proxy);\n> > > \n> > > And here you store it permanently in a map, making its reference count\n> > > permanently = 2 (it goes up to 3, then the local variable goes out of\n> > > scope and gest back to 2).\n> > \n> > I don't think this is a big issue, and reference counting is needed to\n> > support hot-unplug. As we don't support it yet we could turn proxies_\n> > into a std::vector<std::unique_ptr<V4L2CameraProxy>> and devices_ into a\n> > std::map<int, V4L2CameraProxy *>>, knowing that the device won't be\n> > deleted until the V4L2CompatManager itself is deleted. The above lookup\n> > in proxies_ would then become\n> > \n> > \tV4L2CameraProxy *proxy = proxies_[camera_index].get();\n> > \n> > I think the mmaps_ could then map from void * to V4L2CameraProxy *,\n> > which would avoid double look-ups in munmap().\n> \n> Good idea.\n> \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_[newfd] = devices_[oldfd];\n> > > \n> > > Then here you create a new copy, with reference count = 3\n> > > \n> > > At delete time you clear all the entries in devices_, causing the\n> > > shared pointer stored in proxies_ to get back to 1, then proxies_ goes\n> > > out of scope, and the managed object gets deleted.\n> > > \n> > > Why all this dance ? Is the ownership of the CameraProxy shared\n> > > between different componentes? Is it created or destoryed outside of\n> > > this class ? I would save all this complexity and store\n> > > V4L2CameraProxy instances in a vector, and point to them in the map at\n> > > openat() time.\n> > > \n> > > The only thing you care about is to increase the reference count to\n> > > the Camera, but you have a shared_ptr<> class instance in V4L2Camera,\n> > > so that's fine.\n> > \n> > To support hot-unplug we'll likely need shared pointers, for now we can\n> > do without them and use unique_ptr as explained above.\n> > \n> > > > +\n> > > > +\treturn newfd;\n> > > > +}\n> > > > +\n> > > > +int V4L2CompatManager::close(int fd)\n> > > > +{\n> > > > +\tV4L2CameraProxy *proxy = getCamera(fd);\n> > > > +\tif (proxy)\n> > > > +\t\treturn proxy->close();\n> > \n> > This will break if the fd has been dup'ed. Closing one of the two fds\n> > will close the proxy, the second fd will then be unusable. You need to\n> > refcount opens in V4L2CameraProxy.\n> \n> Oh yeah...\n> \n> > You also need to remove fd from the devices_ map here.\n> > \n> > > > +\n> > > > +\tint ret = close_func_(fd);\n> > > > +\treturn ret;\n> > \n> > \treturn close_func_(fd);\n> > \n> > > > +}\n\n[snip]","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 5E755601E4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Dec 2019 13:08:10 +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 98C58A34;\n\tMon, 16 Dec 2019 13:08:09 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1576498089;\n\tbh=kbAnjgm7EQllzVPTgoHHXMBncMbTVMuk6yyM/IE0PqU=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=MTgwKSfuRaYL+5SaRtAUjBmRvDNT3pDM4hX9xLAeYnZlmIdjjDN09mPOyWuXvHRZm\n\tSYDwBnqPftiol7Cu6CUkWzKTEq+NMhSlp6mPWPyfVmvGP6raRj7MVxKM5pWaXYMAAa\n\tyULvjRxGguQvNUWNhEBKyCpGqJCb37KTzYomRPzo=","Date":"Mon, 16 Dec 2019 14:07:28 +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":"<20191216120728.GA4856@pendragon.ideasonboard.com>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>\n\t<20191209103013.jw26qssnewhuumux@uno.localdomain>\n\t<20191216001844.GG4889@pendragon.ideasonboard.com>\n\t<20191216061853.GA5801@emerald.amanokami.net>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20191216061853.GA5801@emerald.amanokami.net>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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":"Mon, 16 Dec 2019 12:08:10 -0000"}},{"id":3268,"web_url":"https://patchwork.libcamera.org/comment/3268/","msgid":"<20191217003135.GK4856@pendragon.ideasonboard.com>","date":"2019-12-17T00:31:35","subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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\nHere's the second part of the review.\n\nOn Mon, Dec 09, 2019 at 11:30:13AM +0100, Jacopo Mondi wrote:\n> On Sun, Dec 08, 2019 at 11:56:03PM -0500, 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> > Note that until we have a way of mapping V4L2 device nodes to libcamera\n> > cameras, the V4L2 compatibility layer will always selects and use the\n> > first enumerated libcamera camera.\n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> >\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             |  30 ++\n> >  src/v4l2/v4l2_camera.cpp         | 299 ++++++++++++++++++++\n> >  src/v4l2/v4l2_camera.h           |  67 +++++\n> >  src/v4l2/v4l2_camera_proxy.cpp   | 452 +++++++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_camera_proxy.h     |  63 +++++\n> >  src/v4l2/v4l2_compat.cpp         |  81 ++++++\n> >  src/v4l2/v4l2_compat_manager.cpp | 382 ++++++++++++++++++++++++++\n> >  src/v4l2/v4l2_compat_manager.h   |  86 ++++++\n> >  10 files changed, 1469 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[snip]\n\n> > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > new file mode 100644\n> > index 00000000..f944c577\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -0,0 +1,299 @@\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> > +#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 \"v4l2_compat_manager.h\"\n> > +\n> > +using namespace libcamera;\n> > +\n> > +LOG_DECLARE_CATEGORY(V4L2Compat);\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> > +V4L2Camera::~V4L2Camera()\n> > +{\n> > +\twhile (!pendingRequests_.empty()) {\n> > +\t\tdelete pendingRequests_.front();\n> > +\t\tpendingRequests_.pop();\n> > +\t}\n\nYou can turn pendingRequests_ into a\nstd::queue<std::unique_ptr<Request>> and remove this.\n\n> > +\n> > +\tbufferLock_.lock();\n> > +\twhile (!completedBuffers_.empty()) {\n> > +\t\tdelete completedBuffers_.front();\n> > +\t\tcompletedBuffers_.pop();\n> > +\t}\n\nSame for the completed buffers. Using std::unique_ptr, beside handling\nautomatic deletion, brings the nice benefit of making the object\nownership rules explicit.\n\n> > +\tbufferLock_.unlock();\n> \n> Is locking required here ? The V4L2Camera is destroyed when the proxy\n> is destroyed, no other calls should be in-flight. Are all buffers\n> dequeued at this point ?\n> \n> > +\n> > +\tcamera_->release();\n> > +}\n> > +\n> > +void V4L2Camera::open(int *ret, bool nonblock)\n> > +{\n> > +\tnonblock_ = nonblock;\n\ns/nonblock/nonBlocking/g\n\nBut even better, I think, would be to pass the bool nonBlocking to\ndqbuf().\n\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> \n> Probably roles should be handled differently, but I'm not sure how.\n> Does our API fall short here ? What if vewifinder is not supported,\n> should we be able to query what roles are supported from a camera ?\n\nI think the camera is then allowed to pick a different role. But in any\ncase I think it's an issue to solve in libcamera if needed, for now this\nshould be fine.\n\n> > +\tif (config_ == nullptr) {\n\n\tif (!config_) {\n\n> > +\t\t*ret = -EINVAL;\n\nShouldn't you release the camera here as open() fails ?\n\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tupdateSizeImage();\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::updateSizeImage()\n> > +{\n> > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > +\tsizeimage_ =\n> > +\t\tV4L2CompatManager::imageSize(\n> \n> nit: this could fit on the previous line\n> \n> > +\t\t\tV4L2CompatManager::drmToV4L2(streamConfig.pixelFormat),\n> > +\t\t\tstreamConfig.size.width,\n> > +\t\t\tstreamConfig.size.height);\n> > +}\n> > +\n> > +void V4L2Camera::requestComplete(Request *request)\n> > +{\n> > +\tif (request->status() == Request::RequestCancelled) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Request not succesfully completed: \"\n> > +\t\t\t\t       << request->status();\n\nThis isn't really an error, it can happen when the camera is stopped\nwhile requests are queued. I would drop the error message.\n\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\t/* We only have one stream at the moment. */\n> > +\tbufferLock_.lock();\n> > +\tBuffer *buffer = request->buffers().begin()->second;\n> > +\tcompletedBuffers_.push(buffer);\n\nQuoting the documentation of Request::addBuffer(),\n\n * Ownership of the buffer is passed to the request. It will be deleted when\n * the request is destroyed after completing.\n\nThe buffer will be deleted once this method returns, and there's no copy\nconstructor for the Buffer class, so you will need to extract the\ninformation you need from the buffer right here, and store them as a\ncustom class in the completedBuffers_ queue. That's just bytesused,\ntimestamp and sequence for now, but you also need to store the buffer\nstate (to handle BufferError correctly in dqbuf) and the index (for a\nreason explained below). Once the buffer rework from Niklas lands, we'll\nhave a FrameMetadata class to store these, and it will be copyable. You\ncan actually already name your custom class FrameMetadata to prepare for\nthat.\n\n> > +\tbufferLock_.unlock();\n> > +\n> > +\tbufferSema_.release();\n> > +}\n> > +\n> > +void V4L2Camera::configure(int *ret, struct v4l2_format *arg,\n> > +\t\t\t   unsigned int bufferCount)\n> > +{\n> > +\tStreamConfiguration &streamConfig = config_->at(0);\n> > +\tstreamConfig.size.width = arg->fmt.pix.width;\n> > +\tstreamConfig.size.height = arg->fmt.pix.height;\n> > +\tstreamConfig.pixelFormat =\n> > +\t\tV4L2CompatManager::v4l2ToDrm(arg->fmt.pix.pixelformat);\n\nI think you should pass a Size and a PixelFormat to this function\ninstead of a v4l2_format, converting from V4L2 to DRM in the caller.\nV4L2Camera would then deal with libcamera structures only, the\nconversion to V4L2 structures would happen in V4L2CameraProxy.\n\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> > +\tupdateSizeImage();\n> > +\tif (sizeimage_ == 0)\n> \n> I'm always a bit scared by functions that updates a global variable as\n> side effect and let the caller responsability of making sure the\n> intended variable is set to the desired value.\n> \n> I would rather make updateSizeImage() a calculateSizeImage(config) and\n> assign it to the the global sizeimage_ in the caller, or make it\n> return an error code.\n> \n> > +\t\t*ret = -EINVAL;\n> > +}\n> > +\n> > +void V4L2Camera::mmap(void **ret, int *err, void *addr, size_t length, int prot, off_t offset)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing MMAP\";\n> > +\n> > +\tif (prot != (PROT_READ | PROT_WRITE)) {\n> > +\t\t*ret = MAP_FAILED;\n> > +\t\t*err = ENOTSUP;\n> > +\t\treturn;\n> > +\t}\n\nYou can move this check to the caller and remove the addr and prot\narguments from this function.\n\n> > +\n> > +\tunsigned int index = offset / sizeimage_;\n> > +\tif (index * sizeimage_ != offset || length != sizeimage_) {\n> > +\t\t*ret = MAP_FAILED;\n> > +\t\t*err = EINVAL;\n> > +\t\treturn;\n> > +\t}\n\nI think sizeimage_ should be cached in V4L2CameraProxy, and this check\nmoved there. You can then replace the offset parameter with an index\nparameter.\n\nTo cache sizeimage_ in the caller, I think you should make configure()\nreturn the stream configuration (through an argument of course, not a\nreturn value as you need to use invokeMethod()), and remove the\ngetStreamConfig() method.\n\n> > +\n> > +\tStream *stream = *camera_->streams().begin();\n> > +\t*ret = stream->buffers()[index].planes()[0].mem();\n> > +\t*err = 0;\n\nAnd you can also remove the err parameter as no error can be returned\nanymore.\n\n> > +}\n> > +\n> > +void V4L2Camera::munmap(int *ret, void *addr, size_t length)\n> > +{\n> > +\t*ret = 0;\n> > +\n> > +\tif (length != sizeimage_)\n> > +\t\t*ret = -EINVAL;\n> > +}\n\nIf sizeimage_ is cached in the V4L2CameraProxy you can move this check\nto the caller and remove this method.\n\n> > +\n> > +void V4L2Camera::validStreamType(bool *ret, uint32_t type)\n> > +{\n> > +\t*ret = (type == V4L2_BUF_TYPE_VIDEO_CAPTURE);\n> > +}\n> > +\n> > +void V4L2Camera::validMemoryType(bool *ret, uint32_t memory)\n> > +{\n> > +\t*ret = (memory == V4L2_MEMORY_MMAP);\n> > +}\n\nCalling this through invokeMethod is a bit expensive for no real gain,\nas the check is completely static. You can just inline these checks in\nvalidateStreamType() and validateMemoryType().\n\nThe rationale for cross-thread calls with invokeMethod() is to avoid\nraces when a non thread-safe method of the Camera object has to be\ncalled from the application thread, while the camera lives in the compat\nmanager thread. For everything else, and especially static validation,\nyou can perform direct calls, or even move some logic to\nV4L2CameraProxy.\n\n> > +\n> > +void V4L2Camera::allocBuffers(int *ret, unsigned int count)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Allocating libcamera bufs\";\n> \n> Empty line, to be consistent with other functions.\n> \n> But I actually wonder if we need this. i had the same in the android\n> HAL and it's a useful tracing tool, so I understand you might want to\n> keep them. After all, debug can be filtered out...\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> > +\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> > +\twhile (!pendingRequests_.empty()) {\n> > +\t\t*ret = camera_->queueRequest(pendingRequests_.front());\n> > +\t\tpendingRequests_.pop();\n> > +\t\tif (*ret < 0)\n> > +\t\t\treturn;\n> > +\t}\n> \n> No error messages ? How does the error log looks like here ?\n> \n> > +}\n> > +\n> > +void V4L2Camera::streamOff(int *ret)\n> > +{\n> > +\t*ret = 0;\n> > +\n> > +\t/* \\todo restore buffers to reqbufs state? */\n\nThis will need to be done, yes. I think you should track buffer states\nin V4L2CameraProxy, likely with a vector of struct v4l2_buffer (let's\nname it V4L2CameraProxy::buffers_). That way you can udpate the state of\neach buffer when needed, and VIDIOC_QUERYBUF will return up-to-date\ninformation with a minimum number of cross-thread calls.\n\nSpeaking of cross-thread calls, I think we should change the strategy a\nlittle bit. What you're doing here is probably fine as a first step, and\nwhat I'm proposing could be implemented on top, but you may want to\nalready go for it.\n\nI think we need to introduce an updateBuffers() method in\nV4L2CameraProxy and a completedBuffers() method in V4L2Camera.\n\nThe latter will simply move the contents of the completedBuffers_ queue\nto a buffer and return it. You should do so protected by the\nbufferLock_, so you can call it directly from V4L2CameraProxy without\ngoing through invokeMethod(), as the method will just access a class\nmember variable without any risk of race thanks to the lock.\n\nIn V4L2CameraProxy::updateBuffers(), you should update each of the\nbuffers in buffers_ from the vector you receive from\nV4L2Camera::completedBuffers(). That way buffers_ will be up-to-date\nwith the current state of the camera.\n\nIn V4L2CameraProxy::dqbuf(), you should first call\nbufferSema_.tryAcquire() or bufferSema_.acquire() based on the\nnon-blocking mode (that should be stored in V4L2CameraProxy and not\nV4L2Camera). There's no need for invokeMethod() there either, the\nSemaphore class is thread-safe. dqbuf() then call updateBuffers(), and\nfinally dequeue the buffer. V4L2Camera::dqbuf() won't be needed anymore,\nall the code that fills v4l2_buffer moves to updateBuffers() or\nV4L2CameraProxy::vidioc_dqbuf().\n\nFinally, V4L2CameraProxy::vidioc_querybuf() calls updateBuffers() and\nthen just copies the info from the requested entry in the buffers_\nvector. V4L2CameraProxy::vidioc_streamoff also updates buffers_ to mark\nall buffers as dequeued. And same for mmap and munmap, they should\nupdate the buffer V4L2_BUF_FLAG_MAPPED flag.\n\nHopefully all this makes sense :-) The number of cross-thread calls\nshould be minimized, and you will be able to remove some state\ninformation from V4L2Camera (such as bufferCount_, nonblock_ and\nsizeimage_).\n\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, struct v4l2_buffer *arg)\n\nLet's pass the buffer index only instead of struct v4l2_buffer.\narg->flags should then be updated in the caller of this method.\n\n> > +{\n> > +\tStream *stream = config_->at(0).stream();\n> > +\tstd::unique_ptr<Buffer> buffer = stream->createBuffer(arg->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> > +\tRequest *request = camera_->createRequest();\n> > +\tif (request == nullptr) {\n> \n> Nit: could you unify on a single pattern ? My preference would be for\n>         if (!request)\n> \n> and I think the style guied suggests it as well\n\nCorrect.\n\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\nYou need to delete request here, or make request a\nstd::unique_ptr<Request>. I think that would be best as pendingRequests_\nshould be a queue of unique pointers.\n\n> > +\t\t*ret = -ENOMEM;\n> > +\t\treturn;\n> > +\t}\n> > +\n> > +\tif (!isRunning_) {\n> > +\t\tpendingRequests_.push(request);\n> > +\t} else {\n> > +\t\t*ret = camera_->queueRequest(request);\n\nThis will become\n\n\t\t*ret = camera_->queueRequest(request.get());\n\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\nAnd here you will need a\n\n\t\trequest.release();\n\n> > +\t}\n> > +\n> > +\targ->flags |= V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags |= V4L2_BUF_FLAG_MAPPED;\n> > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> > +\t*ret = 0;\n> > +}\n> > +\n> > +int V4L2Camera::dqbuf(struct v4l2_buffer *arg)\n> > +{\n> > +\tif (nonblock_ && !bufferSema_.tryAcquire())\n> > +\t\treturn -EAGAIN;\n> > +\telse\n> > +\t\tbufferSema_.acquire();\n> > +\n> > +\tbufferLock_.lock();\n> > +\tBuffer *buffer = completedBuffers_.front();\n> > +\tcompletedBuffers_.pop();\n> > +\tbufferLock_.unlock();\n> > +\n> > +\targ->bytesused = buffer->bytesused();\n> > +\targ->field = V4L2_FIELD_NONE;\n> > +\targ->timestamp.tv_sec = buffer->timestamp() / 1000000000;\n> > +\targ->timestamp.tv_usec = buffer->timestamp() / 1000;\n\n % 1000000\n\n> > +\targ->sequence = buffer->sequence();\n> > +\n> > +\targ->flags &= ~V4L2_BUF_FLAG_QUEUED;\n> > +\targ->flags &= ~V4L2_BUF_FLAG_DONE;\n> \n> Shouldn't the FLAG_DONE bit be set ?\n> \n> > +\n> > +\targ->length = sizeimage_;\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..73a427c4\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -0,0 +1,67 @@\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> > +#ifndef __V4L2_CAMERA_H__\n> > +#define __V4L2_CAMERA_H__\n> > +\n> > +#include <linux/videodev2.h>\n> > +#include <mutex>\n> > +#include <queue>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include \"semaphore.h\"\n> > +\n> > +using namespace libcamera;\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, bool nonblock);\n> > +\tvoid close(int *ret);\n> > +\tvoid getStreamConfig(StreamConfiguration *streamConfig);\n> > +\tvoid requestComplete(Request *request);\n\nThis can be private.\n\n> > +\n> > +\tvoid mmap(void **ret, int *err, void *addr, size_t length,\n> > +\t\t  int prot, off_t offset);\n> > +\tvoid munmap(int *ret, void *addr, size_t length);\n> > +\n> > +\tvoid configure(int *ret, struct v4l2_format *arg,\n> > +\t\t       unsigned int bufferCount);\n> > +\n> > +\tvoid validStreamType(bool *ret, uint32_t type);\n> > +\tvoid validMemoryType(bool *ret, uint32_t memory);\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, struct v4l2_buffer *arg);\n> > +\tint dqbuf(struct v4l2_buffer *arg);\n> > +\n> > +private:\n> > +\tvoid updateSizeImage();\n> > +\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tstd::unique_ptr<CameraConfiguration> config_;\n> > +\n> > +\tunsigned int bufferCount_;\n> > +\tbool isRunning_;\n> > +\tbool nonblock_;\n> > +\n> > +\tunsigned int sizeimage_;\n> > +\n> > +\tSemaphore bufferSema_;\n> > +\tstd::mutex bufferLock_;\n> > +\n> > +\tstd::queue<Request *> pendingRequests_;\n> > +\tstd::queue<Buffer *> 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..66d558e4\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> > @@ -0,0 +1,452 @@\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\nMissing blank line.\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> > +V4L2CameraProxy::~V4L2CameraProxy()\n> > +{\n> > +}\n> \n> Do you need to declare and empty destructor ?\n> \n> > +\n> > +int V4L2CameraProxy::open(bool nonblock)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing OPEN\";\n\nIf you want to keep these debugging messages (we really need a tracing\ninfrastructure...) I would s/OPEN/open()/ and similarly below.\n\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::open, ConnectionTypeBlocking,\n> > +\t\t\t    &ret, nonblock);\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > +\tsetFmtFromConfig();\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int V4L2CameraProxy::close()\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing CLOSE\";\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::close, ConnectionTypeBlocking, &ret);\n> > +\tif (ret < 0) {\n> > +\t\terrno = EIO;\n\nWhy not -ret ?\n\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> > +\tvoid *val;\n> > +\tint err;\n> > +\tvcam_->invokeMethod(&V4L2Camera::mmap, ConnectionTypeBlocking,\n> > +\t\t\t    &val, &err, addr, length, prot, offset);\n> > +\tif (val == MAP_FAILED)\n> > +\t\terrno = err;\n> > +\treturn val;\n> > +}\n> > +\n> > +int V4L2CameraProxy::munmap(void *addr, size_t length)\n> > +{\n> > +\tLOG(V4L2Compat, Debug) << \"Servicing MUNMAP\";\n> > +\n> > +\tint ret;\n> > +\tvcam_->invokeMethod(&V4L2Camera::munmap, ConnectionTypeBlocking,\n> > +\t\t\t    &ret, addr, length);\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +bool V4L2CameraProxy::hasPixelFormat(unsigned int format)\n> > +{\n> > +\tconst std::vector<PixelFormat> &formats =\n> > +\t\tstreamConfig_.formats().pixelformats();\n> > +\treturn std::find(formats.begin(), formats.end(), format) != formats.end();\n> > +}\n> > +\n> > +/* \\todo getDeviceCaps? getMemoryCaps? */\n> > +\n> > +bool V4L2CameraProxy::hasSize(unsigned int format, Size size)\n> > +{\n> > +\tconst std::vector<Size> &sizes = streamConfig_.formats().sizes(format);\n> > +\treturn std::find(sizes.begin(), sizes.end(), size) != sizes.end();\n> > +}\n> > +\n> > +bool V4L2CameraProxy::validateStreamType(uint32_t type)\n> > +{\n> > +\tbool valid;\n> > +\tvcam_->invokeMethod(&V4L2Camera::validStreamType,\n> > +\t\t\t    ConnectionTypeBlocking, &valid, type);\n> > +\tif (!valid)\n> > +\t\treturn false;\n> > +\n> > +\treturn true;\n> \n> To answer you question on the reply to v1: if you prefer this stlye,\n> then it's fine with me. I would have\n>         return valid;\n> and that's it\n> \n> > +}\n> > +\n> > +bool V4L2CameraProxy::validateMemoryType(uint32_t memory)\n> > +{\n> > +\tbool valid;\n> > +\tvcam_->invokeMethod(&V4L2Camera::validMemoryType,\n> > +\t\t\t    ConnectionTypeBlocking, &valid, memory);\n> > +\tif (!valid)\n> > +\t\treturn false;\n> > +\n> > +\treturn true;\n> > +}\n> > +\n> > +void V4L2CameraProxy::setFmtFromConfig()\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> > +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> > +\tcapabilities_.version = KERNEL_VERSION(5, 2, 0);\n> \n> This should come from some library wide header I guess. Having it\n> hard-coded here won't scale. Not for this series probably, but is\n> worth a todo I guess\n> \n> > +\tcapabilities_.device_caps = V4L2_CAP_VIDEO_CAPTURE;\n> > +\tcapabilities_.capabilities =\n> > +\t\tcapabilities_.device_caps | V4L2_CAP_DEVICE_CAPS;\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\treturn -EINVAL;\n> > +\tif (arg->index > streamConfig_.formats().pixelformats().size())\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tmemcpy(arg->description, \"asdf\", 5);\n> \n> If we don't want to introduce the format->description map now, add a\n> todo please\n> \n> > +\targ->pixelformat =\n> > +\t\tV4L2CompatManager::drmToV4L2(\n> \n> Nit: fits on the previous line\n> \n> > +\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> > +\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,\n> > +\t\t\t    &ret, arg, bufferCount_);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\n> > +\tvcam_->invokeMethod(&V4L2Camera::getStreamConfig,\n> > +\t\t\t    ConnectionTypeBlocking, &streamConfig_);\n> > +\tsetFmtFromConfig();\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> > +\tif (!hasPixelFormat(format))\n> > +\t\tformat = streamConfig_.formats().pixelformats()[0];\n> > +\n> > +\tSize size(arg->fmt.pix.width, arg->fmt.pix.height);\n> > +\tif (!hasSize(format, size))\n> > +\t\tsize = streamConfig_.formats().sizes(format)[0];\n\nAs hasPixelFormat() and hasSize() are only used here, I would inline\nthem.\n\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> \n> Nit: hard to parse\n> \n> \targ->fmt.pix.bytesperline = V4L2CompatManager::bplMultiplier(\n> \t\t\t                V4L2CompatManager::drmToV4L2(format)) *\n>                                         arg->fmt.pix.width;\n> \n>  ?\n> \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> > +\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\treturn -EINVAL;\n> > +\tif (!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,\n> > +\t\t\t    &ret, &curV4L2Format_, arg->count);\n> > +\tif (ret < 0)\n> > +\t\treturn -EINVAL;\n> > +\targ->count = streamConfig_.bufferCount;\n> > +\tbufferCount_ = arg->count;\n> > +\n> > +\tif (arg->memory != V4L2_MEMORY_MMAP)\n> > +\t\treturn -EINVAL;\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\treturn -EINVAL;\n> > +\tif (arg->index >= stream->buffers().size())\n> > +\t\treturn -EINVAL;\n> \n> Nit: could be made a single condition, up to you\n> \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> \n> So, the validation of the buffer and memory type is performed by\n> invoking a method on the V4L2Camera, while we here hardcode them in\n> the proxy... Not big, but I would rather keep the information here or\n> there (I would drop the method call for the validation, as so far we\n> only support one type)\n> \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\treturn -EINVAL;\n> > +\tif (!validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\tif (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);\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\treturn -EINVAL;\n> > +\tif (!validateMemoryType(arg->memory))\n> > +\t\treturn -EINVAL;\n> > +\n> > +\targ->index = currentBuf_;\n> > +\tcurrentBuf_ = (currentBuf_ + 1) % bufferCount_;\n> > +\n> > +\treturn vcam_->dqbuf(arg);\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> > +\tcase VIDIOC_EXPBUF:\n> > +\tcase VIDIOC_ENUM_FRAMESIZES:\n> \n> Aren't there more ioctl calls ? Why are these two special ? Can't they\n> be catched by default like the others ?\n\nIt's not strictly needed indeed, but I don't mind them as reminders that\nthose two ioctls are on our roadmap. A \"\\todo Implement the ioctls\nbelow\" would be even better.\n\n> > +\tdefault:\n> > +\t\tret = -ENOTTY;\n\nWhile not strictly necessary, I would add a break here for consistency.\n\n> > +\t}\n> > +\n> > +\tif (ret < 0) {\n> > +\t\terrno = -ret;\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\terrno = 0;\n\nQuoting the errno man page,\n\n\"The value of errno is never set to zero by any system call or library\nfunction.\".\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..64c7aadd\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_camera_proxy.h\n> > @@ -0,0 +1,63 @@\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> > +#ifndef __V4L2_CAMERA_PROXY_H__\n> > +#define __V4L2_CAMERA_PROXY_H__\n> > +\n> > +#include <linux/videodev2.h>\n\nYou should also include <memory> for std::shared_ptr, and <sys/types.h>\nfor off_t and size_t.\n\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> > +\t~V4L2CameraProxy();\n> > +\n> > +\tint open(bool nonblock);\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> > +private:\n> > +\tbool hasPixelFormat(unsigned int format);\n> > +\tbool hasSize(unsigned int format, Size size);\n> > +\tbool validateStreamType(uint32_t type);\n> > +\tbool validateMemoryType(uint32_t memory);\n> > +\tvoid setFmtFromConfig();\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 index_;\n> > +\n> > +\tstruct v4l2_format curV4L2Format_;\n> > +\tStreamConfiguration streamConfig_;\n> > +\tstruct v4l2_capability capabilities_;\n> > +\tunsigned int bufferCount_;\n> > +\tunsigned int currentBuf_;\n> > +\n> > +\tstd::unique_ptr<V4L2Camera> vcam_;\n> > +};\n> > +\n> > +#endif /* __V4L2_CAMERA_PROXY_H__ */\n\n[snip]\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..90416b35\n> > --- /dev/null\n> > +++ b/src/v4l2/v4l2_compat_manager.cpp\n> > @@ -0,0 +1,382 @@\n\n[snip]\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> > +\tif (std::string(path).find(\"video\") == std::string::npos)\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, Error) << \"No camera found for \" << path;\n> > +\t\treturn fd;\n> > +\t}\n> > +\n> > +\tunsigned int camera_index = static_cast<unsigned int>(ret);\n> > +\n> > +\tstd::shared_ptr<V4L2CameraProxy> proxy = proxies_[camera_index];\n> \n> You here share the ownership of the managed object owned by\n> proxies_[camera_index] with a local variable, increasing its\n> reference count\n> \n> > +\tret = proxy->open(mode & O_NONBLOCK);\n\nO_NONBLOCK, as well as O_CLOEXEC below, are part of the flags, not of the\nmode.\n\nI think you should also check other flags and reject the ones that we\ndon't support (or maybe all flags but the ones we support).\n\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tint efd = eventfd(0, (mode & O_CLOEXEC) | (mode & O_NONBLOCK));\n> > +\tif (efd < 0)\n> > +\t\treturn efd;\n> \n> I'm still not convinced about this, I'll go read your reply to v1\n> again.\n> \n> > +\n> > +\tdevices_.emplace(efd, proxy);\n> \n> And here you store it permanently in a map, making its reference count\n> permanently = 2 (it goes up to 3, then the local variable goes out of\n> scope and gest back to 2).\n> > +\n> > +\treturn efd;\n> > +}\n\n[snip]","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 26333601E7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 17 Dec 2019 01:31:46 +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 4C9519C5;\n\tTue, 17 Dec 2019 01:31:45 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1576542705;\n\tbh=t21hvyt2kjzzlUw7JTx2P3LYWsYXOLbZIKCw5Wbxjpo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=HA7KVE3pmfZibcLPWw9uOM3N4GZyVEQvtkPl5l1qlLieKzdXXr76zc4ENeP9hM9w1\n\tG0efoMdS/WyKIL2UjHuESuhL2AZGT7Mi8r/G59OTMhxZrxvqRwEOXr0aveP1gF7c7e\n\tSV2KVeMJ/i4QqHRGTt/TdqgUlBVkT/yfhFZ9sKhE=","Date":"Tue, 17 Dec 2019 02:31:35 +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":"<20191217003135.GK4856@pendragon.ideasonboard.com>","References":"<20191209045603.6245-1-paul.elder@ideasonboard.com>\n\t<20191209045603.6245-2-paul.elder@ideasonboard.com>\n\t<20191209103013.jw26qssnewhuumux@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20191209103013.jw26qssnewhuumux@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 2/2] 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, 17 Dec 2019 00:31:46 -0000"}}]