[libcamera-devel] [PATCH v2 02/17] v4l2: v4l2_compat: Support multiple open

Paul Elder paul.elder at ideasonboard.com
Fri Jun 19 07:41:08 CEST 2020


Previously, since we acquired the libcamera camera upon open(), it was
impossible to support multiple open, as any subsequent opens would
return error because the camera would already be acquired.

To fix this, we first initialize the camera in the first call to
V4L2CameraProxy::open(), just to heat up the stream format cache. We
then add an exclusive lock on the V4L2Camera. All vidioc ioctls prior to
reqbufs > 0 (except for s_fmt) are able to access the camera without
this exclusive lock. A call to reqbufs > 0 (and s_fmt) will take the
lock, and the lock will be released at reqbufs = 0. While the lock is
taken, the eventfd that should be signaled (and cleared) by V4L2Camera
and V4L2CameraProxy is set to the fd that has taken the lock, and is
cleared when the lock is released. In case close() is called without a
reqbufs = 0 first, the lock is also released on close().

We also use the V4L2CameraFile to contain all the information specific
to an open instance of the file. This removes the need to keep track of
such information within V4L2CameraProxy via multiple maps from int fd to
info. We also handle dup in V4L2CompatManager instead, since that is
just another int fd pointing to the same V4L2CameraFile instance.
V4L2CompatManager is also modified to access the V4L2CameraProxy through
the V4L2CameraFile.

Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>

---
Changes in v2:
- use V4L2CameraFile in V4L2CompatManager to deal with dup, and in
  V4L2CameraProxy to deal with information specific to the open file
  (nonblock and priority)
- unlock exclusive camera lock on close()
---
 src/v4l2/v4l2_camera.cpp         |   9 +-
 src/v4l2/v4l2_camera.h           |   1 +
 src/v4l2/v4l2_camera_file.cpp    |   4 +-
 src/v4l2/v4l2_camera_proxy.cpp   | 201 ++++++++++++++++++++++---------
 src/v4l2/v4l2_camera_proxy.h     |  47 +++++---
 src/v4l2/v4l2_compat_manager.cpp |  71 +++++------
 src/v4l2/v4l2_compat_manager.h   |   4 +-
 7 files changed, 215 insertions(+), 122 deletions(-)

diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp
index 9a1ebc8..2557320 100644
--- a/src/v4l2/v4l2_camera.cpp
+++ b/src/v4l2/v4l2_camera.cpp
@@ -17,7 +17,8 @@ using namespace libcamera;
 LOG_DECLARE_CATEGORY(V4L2Compat);
 
 V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)
-	: camera_(camera), isRunning_(false), bufferAllocator_(nullptr)
+	: camera_(camera), isRunning_(false), bufferAllocator_(nullptr),
+	  efd_(-1)
 {
 	camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);
 }
@@ -29,7 +30,6 @@ V4L2Camera::~V4L2Camera()
 
 int V4L2Camera::open()
 {
-	/* \todo Support multiple open. */
 	if (camera_->acquire() < 0) {
 		LOG(V4L2Compat, Error) << "Failed to acquire camera";
 		return -EINVAL;
@@ -59,6 +59,11 @@ void V4L2Camera::bind(int efd)
 	efd_ = efd;
 }
 
+void V4L2Camera::unbind()
+{
+	efd_ = -1;
+}
+
 void V4L2Camera::getStreamConfig(StreamConfiguration *streamConfig)
 {
 	*streamConfig = config_->at(0);
diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h
index 33f5eb0..30114ed 100644
--- a/src/v4l2/v4l2_camera.h
+++ b/src/v4l2/v4l2_camera.h
@@ -40,6 +40,7 @@ public:
 	int open();
 	void close();
 	void bind(int efd);
+	void unbind();
 	void getStreamConfig(StreamConfiguration *streamConfig);
 	std::vector<Buffer> completedBuffers();
 
diff --git a/src/v4l2/v4l2_camera_file.cpp b/src/v4l2/v4l2_camera_file.cpp
index 8916729..d07b936 100644
--- a/src/v4l2/v4l2_camera_file.cpp
+++ b/src/v4l2/v4l2_camera_file.cpp
@@ -21,12 +21,12 @@ V4L2CameraFile::V4L2CameraFile(int efd, bool nonBlocking, V4L2CameraProxy *proxy
 	: priority_(V4L2_PRIORITY_DEFAULT), proxy_(proxy),
 	  nonBlocking_(nonBlocking), efd_(efd)
 {
-	proxy_->open(nonBlocking);
+	proxy_->open(this);
 }
 
 V4L2CameraFile::~V4L2CameraFile()
 {
-	proxy_->close();
+	proxy_->close(this);
 }
 
 V4L2CameraProxy *V4L2CameraFile::proxy()
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index bf47aa7..f06f58d 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -23,6 +23,7 @@
 #include "libcamera/internal/utils.h"
 
 #include "v4l2_camera.h"
+#include "v4l2_camera_file.h"
 #include "v4l2_compat_manager.h"
 
 #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
@@ -34,45 +35,57 @@ LOG_DECLARE_CATEGORY(V4L2Compat);
 V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
 				 std::shared_ptr<Camera> camera)
 	: refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
-	  vcam_(std::make_unique<V4L2Camera>(camera))
+	  vcam_(std::make_unique<V4L2Camera>(camera)), efd_(-1),
+	  acquiredCf_(nullptr), initialized_(false)
 {
 	querycap(camera);
 }
 
-int V4L2CameraProxy::open(bool nonBlocking)
+int V4L2CameraProxy::open(V4L2CameraFile *cf)
 {
-	LOG(V4L2Compat, Debug) << "Servicing open";
+	LOG(V4L2Compat, Debug) << "Servicing open fd = " << cf->efd();
 
-	int ret = vcam_->open();
-	if (ret < 0) {
-		errno = -ret;
-		return -1;
-	}
+	refcount_++;
+
+	if (initialized_)
+		return 0;
 
-	nonBlocking_ = nonBlocking;
+	/*
+	 * We will open the camera here, once, and keep it open until the last
+	 * V4L2CameraFile is closed. Before any reqbufs with count > 0 is
+	 * called, anybody can call any ioctl. Once reqbufs is called with
+	 * count > 0, the * exclusive lock will be assigned to that
+	 * V4L2CameraFile, in acquiredCf_. At this point, no other fd can call
+	 * any ioctl (except for querycap, try_fmt, g/s_priority, and
+	 * enum/g/s_input), and they will return -EBUSY. After reqbufs is
+	 * called with count = 0, the exclusive lock will be released.
+	 */
+
+	initialized_ = true;
+
+	int ret = vcam_->open();
+	if (ret < 0)
+		return ret;
 
 	vcam_->getStreamConfig(&streamConfig_);
 	setFmtFromConfig(streamConfig_);
 	sizeimage_ = calculateSizeImage(streamConfig_);
 
-	refcount_++;
-
 	return 0;
 }
 
-void V4L2CameraProxy::dup()
+void V4L2CameraProxy::close(V4L2CameraFile *cf)
 {
-	refcount_++;
-}
+	LOG(V4L2Compat, Debug) << "Servicing close fd = " << cf->efd();
 
-void V4L2CameraProxy::close()
-{
-	LOG(V4L2Compat, Debug) << "Servicing close";
+	unlock(cf);
 
 	if (--refcount_ > 0)
 		return;
 
 	vcam_->close();
+
+	initialized_ = false;
 }
 
 void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
@@ -221,9 +234,10 @@ int V4L2CameraProxy::vidioc_querycap(struct v4l2_capability *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)
+int V4L2CameraProxy::vidioc_enum_fmt(V4L2CameraFile *cf, struct v4l2_fmtdesc *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt fd = " << cf->efd();
+
 
 	if (!validateBufferType(arg->type) ||
 	    arg->index >= streamConfig_.formats().pixelformats().size())
@@ -237,9 +251,10 @@ int V4L2CameraProxy::vidioc_enum_fmt(struct v4l2_fmtdesc *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_g_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_g_fmt(V4L2CameraFile *cf, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt fd = " << cf->efd();
+
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -275,9 +290,13 @@ void V4L2CameraProxy::tryFormat(struct v4l2_format *arg)
 	arg->fmt.pix.colorspace   = V4L2_COLORSPACE_SRGB;
 }
 
-int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_s_fmt(V4L2CameraFile *cf, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -285,9 +304,9 @@ int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)
 	tryFormat(arg);
 
 	Size size(arg->fmt.pix.width, arg->fmt.pix.height);
-	int ret = vcam_->configure(&streamConfig_, size,
-				   v4l2ToDrm(arg->fmt.pix.pixelformat),
-				   bufferCount_);
+	ret = vcam_->configure(&streamConfig_, size,
+			       v4l2ToDrm(arg->fmt.pix.pixelformat),
+			       bufferCount_);
 	if (ret < 0)
 		return -EINVAL;
 
@@ -302,9 +321,9 @@ int V4L2CameraProxy::vidioc_s_fmt(struct v4l2_format *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_try_fmt(struct v4l2_format *arg)
+int V4L2CameraProxy::vidioc_try_fmt(V4L2CameraFile *cf, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt fd = " << cf->efd();
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -329,11 +348,13 @@ int V4L2CameraProxy::freeBuffers()
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
+int V4L2CameraProxy::vidioc_reqbufs(V4L2CameraFile *cf, struct v4l2_requestbuffers *arg)
 {
-	int ret;
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs fd = " << cf->efd();
 
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs";
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    !validateMemoryType(arg->memory))
@@ -342,9 +363,21 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
 	LOG(V4L2Compat, Debug) << arg->count << " buffers requested ";
 
 	arg->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
+	memset(arg->reserved, 0, sizeof(arg->reserved));
 
-	if (arg->count == 0)
+	if (arg->count == 0) {
+		unlock(cf);
 		return freeBuffers();
+	}
+
+	if (bufferCount_ > 0) {
+		ret = freeBuffers();
+		if (ret < 0) {
+			LOG(V4L2Compat, Error)
+				<< "Failed to free libcamera buffers for re-reqbufs";
+			return ret;
+		}
+	}
 
 	Size size(curV4L2Format_.fmt.pix.width, curV4L2Format_.fmt.pix.height);
 	ret = vcam_->configure(&streamConfig_, size,
@@ -387,6 +420,7 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
 		buf.memory = V4L2_MEMORY_MMAP;
 		buf.m.offset = i * curV4L2Format_.fmt.pix.sizeimage;
 		buf.index = i;
+		buf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
 
 		buffers_[i] = buf;
 	}
@@ -396,9 +430,13 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_querybuf(V4L2CameraFile *cf, struct v4l2_buffer *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    arg->index >= bufferCount_)
@@ -411,17 +449,21 @@ int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_qbuf(V4L2CameraFile *cf, struct v4l2_buffer *arg)
 {
 	LOG(V4L2Compat, Debug) << "Servicing vidioc_qbuf, index = "
-			       << arg->index;
+			       << arg->index << " fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    !validateMemoryType(arg->memory) ||
 	    arg->index >= bufferCount_)
 		return -EINVAL;
 
-	int ret = vcam_->qbuf(arg->index);
+	ret = vcam_->qbuf(arg->index);
 	if (ret < 0)
 		return ret;
 
@@ -431,15 +473,19 @@ int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
 	return ret;
 }
 
-int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *cf, struct v4l2_buffer *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    !validateMemoryType(arg->memory))
 		return -EINVAL;
 
-	if (!nonBlocking_)
+	if (!(cf->nonBlocking()))
 		vcam_->bufferSema_.acquire();
 	else if (!vcam_->bufferSema_.tryAcquire())
 		return -EAGAIN;
@@ -455,16 +501,20 @@ int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)
 	currentBuf_ = (currentBuf_ + 1) % bufferCount_;
 
 	uint64_t data;
-	int ret = ::read(efd_, &data, sizeof(data));
+	ret = ::read(efd_, &data, sizeof(data));
 	if (ret != sizeof(data))
 		LOG(V4L2Compat, Error) << "Failed to clear eventfd POLLIN";
 
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_streamon(int *arg)
+int V4L2CameraProxy::vidioc_streamon(V4L2CameraFile *cf, int *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(*arg))
 		return -EINVAL;
@@ -474,14 +524,18 @@ int V4L2CameraProxy::vidioc_streamon(int *arg)
 	return vcam_->streamOn();
 }
 
-int V4L2CameraProxy::vidioc_streamoff(int *arg)
+int V4L2CameraProxy::vidioc_streamoff(V4L2CameraFile *cf, int *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff fd = " << cf->efd();
+
+	int ret = lock(cf);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(*arg))
 		return -EINVAL;
 
-	int ret = vcam_->streamOff();
+	ret = vcam_->streamOff();
 
 	for (struct v4l2_buffer &buf : buffers_)
 		buf.flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE);
@@ -489,7 +543,7 @@ int V4L2CameraProxy::vidioc_streamoff(int *arg)
 	return ret;
 }
 
-int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
+int V4L2CameraProxy::ioctl(V4L2CameraFile *cf, unsigned long request, void *arg)
 {
 	int ret;
 	switch (request) {
@@ -497,34 +551,34 @@ int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
 		ret = vidioc_querycap(static_cast<struct v4l2_capability *>(arg));
 		break;
 	case VIDIOC_ENUM_FMT:
-		ret = vidioc_enum_fmt(static_cast<struct v4l2_fmtdesc *>(arg));
+		ret = vidioc_enum_fmt(cf, static_cast<struct v4l2_fmtdesc *>(arg));
 		break;
 	case VIDIOC_G_FMT:
-		ret = vidioc_g_fmt(static_cast<struct v4l2_format *>(arg));
+		ret = vidioc_g_fmt(cf, static_cast<struct v4l2_format *>(arg));
 		break;
 	case VIDIOC_S_FMT:
-		ret = vidioc_s_fmt(static_cast<struct v4l2_format *>(arg));
+		ret = vidioc_s_fmt(cf, static_cast<struct v4l2_format *>(arg));
 		break;
 	case VIDIOC_TRY_FMT:
-		ret = vidioc_try_fmt(static_cast<struct v4l2_format *>(arg));
+		ret = vidioc_try_fmt(cf, static_cast<struct v4l2_format *>(arg));
 		break;
 	case VIDIOC_REQBUFS:
-		ret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));
+		ret = vidioc_reqbufs(cf, static_cast<struct v4l2_requestbuffers *>(arg));
 		break;
 	case VIDIOC_QUERYBUF:
-		ret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_querybuf(cf, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_QBUF:
-		ret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_qbuf(cf, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_DQBUF:
-		ret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_dqbuf(cf, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_STREAMON:
-		ret = vidioc_streamon(static_cast<int *>(arg));
+		ret = vidioc_streamon(cf, static_cast<int *>(arg));
 		break;
 	case VIDIOC_STREAMOFF:
-		ret = vidioc_streamoff(static_cast<int *>(arg));
+		ret = vidioc_streamoff(cf, static_cast<int *>(arg));
 		break;
 	default:
 		ret = -ENOTTY;
@@ -539,10 +593,37 @@ int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
 	return ret;
 }
 
-void V4L2CameraProxy::bind(int fd)
+/**
+ * \brief Acquire an exclusive lock on the V4L2Camera
+ *
+ * \return Zero on success or if already acquired, and negative error on
+ * failure.
+ */
+int V4L2CameraProxy::lock(V4L2CameraFile *cf)
+{
+	if (acquiredCf_ == cf)
+		return 0;
+
+	if (acquiredCf_)
+		return -EBUSY;
+
+	efd_ = cf->efd();
+	vcam_->bind(efd_);
+
+	acquiredCf_ = cf;
+
+	return 0;
+}
+
+void V4L2CameraProxy::unlock(V4L2CameraFile *cf)
 {
-	efd_ = fd;
-	vcam_->bind(fd);
+	if (acquiredCf_ != cf)
+		return;
+
+	efd_ = -1;
+	vcam_->unbind();
+
+	acquiredCf_ = nullptr;
 }
 
 struct PixelFormatPlaneInfo {
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 27d3e50..43290ca 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -21,20 +21,19 @@
 
 using namespace libcamera;
 
+class V4L2CameraFile;
+
 class V4L2CameraProxy
 {
 public:
 	V4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);
 
-	int open(bool nonBlocking);
-	void dup();
-	void close();
+	int open(V4L2CameraFile *cfile);
+	void close(V4L2CameraFile *cf);
 	void *mmap(void *addr, size_t length, int prot, int flags, off64_t offset);
 	int munmap(void *addr, size_t length);
 
-	int ioctl(unsigned long request, void *arg);
-
-	void bind(int fd);
+	int ioctl(V4L2CameraFile *cf, unsigned long request, void *arg);
 
 private:
 	bool validateBufferType(uint32_t type);
@@ -47,16 +46,19 @@ private:
 	int freeBuffers();
 
 	int vidioc_querycap(struct v4l2_capability *arg);
-	int vidioc_enum_fmt(struct v4l2_fmtdesc *arg);
-	int vidioc_g_fmt(struct v4l2_format *arg);
-	int vidioc_s_fmt(struct v4l2_format *arg);
-	int vidioc_try_fmt(struct v4l2_format *arg);
-	int vidioc_reqbufs(struct v4l2_requestbuffers *arg);
-	int vidioc_querybuf(struct v4l2_buffer *arg);
-	int vidioc_qbuf(struct v4l2_buffer *arg);
-	int vidioc_dqbuf(struct v4l2_buffer *arg);
-	int vidioc_streamon(int *arg);
-	int vidioc_streamoff(int *arg);
+	int vidioc_enum_fmt(V4L2CameraFile *cf, struct v4l2_fmtdesc *arg);
+	int vidioc_g_fmt(V4L2CameraFile *cf, struct v4l2_format *arg);
+	int vidioc_s_fmt(V4L2CameraFile *cf, struct v4l2_format *arg);
+	int vidioc_try_fmt(V4L2CameraFile *cf, struct v4l2_format *arg);
+	int vidioc_reqbufs(V4L2CameraFile *cf, struct v4l2_requestbuffers *arg);
+	int vidioc_querybuf(V4L2CameraFile *cf, struct v4l2_buffer *arg);
+	int vidioc_qbuf(V4L2CameraFile *cf, struct v4l2_buffer *arg);
+	int vidioc_dqbuf(V4L2CameraFile *cf, struct v4l2_buffer *arg);
+	int vidioc_streamon(V4L2CameraFile *cf, int *arg);
+	int vidioc_streamoff(V4L2CameraFile *cf, int *arg);
+
+	int lock(V4L2CameraFile *cf);
+	void unlock(V4L2CameraFile *cf);
 
 	static unsigned int bplMultiplier(uint32_t format);
 	static unsigned int imageSize(uint32_t format, unsigned int width,
@@ -67,7 +69,6 @@ private:
 
 	unsigned int refcount_;
 	unsigned int index_;
-	bool nonBlocking_;
 
 	struct v4l2_format curV4L2Format_;
 	StreamConfiguration streamConfig_;
@@ -82,6 +83,18 @@ private:
 	std::unique_ptr<V4L2Camera> vcam_;
 
 	int efd_;
+
+	/*
+	 * This is an exclusive lock on the camera. When this is not taken,
+	 * anybody can call any ioctl before reqbufs. reqbufs with count > 0
+	 * will claim this lock, and reqbufs with count = 0 will release it.
+	 * Any ioctl is called while the lock is taken will return -EBUSY
+	 * (if applicable, eg. try_fmt, querycap, g/s_priority, and
+	 * enum/g/s_input are exempt).
+	 */
+	V4L2CameraFile *acquiredCf_;
+
+	bool initialized_;
 };
 
 #endif /* __V4L2_CAMERA_PROXY_H__ */
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 8da3316..9cede76 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -24,6 +24,8 @@
 
 #include "libcamera/internal/log.h"
 
+#include "v4l2_camera_file.h"
+
 using namespace libcamera;
 
 LOG_DEFINE_CATEGORY(V4L2Compat)
@@ -49,7 +51,7 @@ V4L2CompatManager::V4L2CompatManager()
 
 V4L2CompatManager::~V4L2CompatManager()
 {
-	devices_.clear();
+	cameraFiles_.clear();
 	mmaps_.clear();
 
 	if (cm_) {
@@ -95,13 +97,13 @@ V4L2CompatManager *V4L2CompatManager::instance()
 	return &instance;
 }
 
-V4L2CameraProxy *V4L2CompatManager::getProxy(int fd)
+std::shared_ptr<V4L2CameraFile> V4L2CompatManager::getCameraFile(int fd)
 {
-	auto device = devices_.find(fd);
-	if (device == devices_.end())
-		return nullptr;
+	auto cameraFile = cameraFiles_.find(fd);
+	if (cameraFile == cameraFiles_.end())
+		return std::shared_ptr<V4L2CameraFile>();
 
-	return device->second;
+	return cameraFile->second;
 }
 
 int V4L2CompatManager::getCameraIndex(int fd)
@@ -148,25 +150,17 @@ int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mod
 
 	fops_.close(fd);
 
-	unsigned int camera_index = static_cast<unsigned int>(ret);
-
-	V4L2CameraProxy *proxy = proxies_[camera_index].get();
-	ret = proxy->open(oflag & O_NONBLOCK);
-	if (ret < 0)
-		return ret;
-
 	int efd = eventfd(0, EFD_SEMAPHORE |
 			     ((oflag & O_CLOEXEC) ? EFD_CLOEXEC : 0) |
 			     ((oflag & O_NONBLOCK) ? EFD_NONBLOCK : 0));
-	if (efd < 0) {
-		int err = errno;
-		proxy->close();
-		errno = err;
+	if (efd < 0)
 		return efd;
-	}
 
-	proxy->bind(efd);
-	devices_.emplace(efd, proxy);
+	unsigned int camera_index = static_cast<unsigned int>(ret);
+
+	V4L2CameraProxy *proxy = proxies_[camera_index].get();
+
+	cameraFiles_.emplace(efd, std::make_shared<V4L2CameraFile>(efd, oflag & O_NONBLOCK, proxy));
 
 	return efd;
 }
@@ -177,40 +171,39 @@ int V4L2CompatManager::dup(int oldfd)
 	if (newfd < 0)
 		return newfd;
 
-	auto device = devices_.find(oldfd);
-	if (device != devices_.end()) {
-		V4L2CameraProxy *proxy = device->second;
-		devices_[newfd] = proxy;
-		proxy->dup();
-	}
+	auto cameraFile = cameraFiles_.find(oldfd);
+	if (cameraFile != cameraFiles_.end())
+		cameraFiles_[newfd] = cameraFile->second;
 
 	return newfd;
 }
 
 int V4L2CompatManager::close(int fd)
 {
-	V4L2CameraProxy *proxy = getProxy(fd);
-	if (proxy) {
-		proxy->close();
-		devices_.erase(fd);
-		return 0;
-	}
+	std::shared_ptr<V4L2CameraFile> cameraFile = getCameraFile(fd);
+	if (cameraFile)
+		cameraFiles_.erase(fd);
 
+	/* We still need to close the eventfd. */
 	return fops_.close(fd);
 }
 
 void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,
 			      int fd, off64_t offset)
 {
-	V4L2CameraProxy *proxy = getProxy(fd);
-	if (!proxy)
+	std::shared_ptr<V4L2CameraFile> cameraFile = getCameraFile(fd);
+	if (!cameraFile)
 		return fops_.mmap(addr, length, prot, flags, fd, offset);
 
-	void *map = proxy->mmap(addr, length, prot, flags, offset);
+	void *map = cameraFile->proxy()->mmap(addr, length, prot, flags, offset);
 	if (map == MAP_FAILED)
 		return map;
 
-	mmaps_[map] = proxy;
+	/*
+	 * Map to V4L2CameraProxy directly to prevent adding more references
+	 * to V4L2CameraFile.
+	 */
+	mmaps_[map] = cameraFile->proxy();
 	return map;
 }
 
@@ -233,9 +226,9 @@ int V4L2CompatManager::munmap(void *addr, size_t length)
 
 int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)
 {
-	V4L2CameraProxy *proxy = getProxy(fd);
-	if (!proxy)
+	std::shared_ptr<V4L2CameraFile> cameraFile = getCameraFile(fd);
+	if (!cameraFile)
 		return fops_.ioctl(fd, request, arg);
 
-	return proxy->ioctl(request, arg);
+	return cameraFile->proxy()->ioctl(cameraFile.get(), request, arg);
 }
diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h
index 3d4e512..b92f147 100644
--- a/src/v4l2/v4l2_compat_manager.h
+++ b/src/v4l2/v4l2_compat_manager.h
@@ -44,7 +44,7 @@ public:
 
 	static V4L2CompatManager *instance();
 
-	V4L2CameraProxy *getProxy(int fd);
+	std::shared_ptr<V4L2CameraFile> getCameraFile(int fd);
 	const FileOperations &fops() const { return fops_; }
 
 	int openat(int dirfd, const char *path, int oflag, mode_t mode);
@@ -68,7 +68,7 @@ private:
 	CameraManager *cm_;
 
 	std::vector<std::unique_ptr<V4L2CameraProxy>> proxies_;
-	std::map<int, V4L2CameraProxy *> devices_;
+	std::map<int, std::shared_ptr<V4L2CameraFile>> cameraFiles_;
 	std::map<void *, V4L2CameraProxy *> mmaps_;
 };
 
-- 
2.27.0



More information about the libcamera-devel mailing list