[libcamera-devel,01/15] v4l2: v4l2_compat: Support multiple open

Message ID 20200616131244.70308-2-paul.elder@ideasonboard.com
State Superseded
Headers show
Series
  • Support v4l2-compliance
Related show

Commit Message

Paul Elder June 16, 2020, 1:12 p.m. UTC
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 add a mapping of fd to boolean to keep track of which fds opened
the camera in non-blocking mode.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 src/v4l2/v4l2_camera.cpp         |   9 +-
 src/v4l2/v4l2_camera.h           |   1 +
 src/v4l2/v4l2_camera_proxy.cpp   | 202 ++++++++++++++++++++++---------
 src/v4l2/v4l2_camera_proxy.h     |  47 ++++---
 src/v4l2/v4l2_compat_manager.cpp |  25 ++--
 5 files changed, 194 insertions(+), 90 deletions(-)

Comments

Laurent Pinchart June 17, 2020, 1:04 a.m. UTC | #1
Hi Paul,

Thank you for the patch.

On Tue, Jun 16, 2020 at 10:12:30PM +0900, Paul Elder wrote:
> 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 add a mapping of fd to boolean to keep track of which fds opened
> the camera in non-blocking mode.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
>  src/v4l2/v4l2_camera.cpp         |   9 +-
>  src/v4l2/v4l2_camera.h           |   1 +
>  src/v4l2/v4l2_camera_proxy.cpp   | 202 ++++++++++++++++++++++---------
>  src/v4l2/v4l2_camera_proxy.h     |  47 ++++---
>  src/v4l2/v4l2_compat_manager.cpp |  25 ++--
>  5 files changed, 194 insertions(+), 90 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_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
> index 13db428..594dd13 100644
> --- a/src/v4l2/v4l2_camera_proxy.cpp
> +++ b/src/v4l2/v4l2_camera_proxy.cpp
> @@ -33,45 +33,68 @@ 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),
> +	  acquiredFd_(-1), initialized_(false)
>  {
>  	querycap(camera);
>  }
>  
> -int V4L2CameraProxy::open(bool nonBlocking)
> +int V4L2CameraProxy::open(int fd, bool nonBlocking)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing open";
> +	LOG(V4L2Compat, Debug) << "Servicing open fd = " << fd;
>  
> -	int ret = vcam_->open();
> -	if (ret < 0) {
> -		errno = -ret;
> -		return -1;
> -	}
> +	nonBlockingFds_[fd] = nonBlocking;
>  
> -	nonBlocking_ = nonBlocking;
> +	refcount_++;
> +
> +	if (initialized_)
> +		return 0;
> +
> +	/*
> +	 * We will open the camera here, once, and keep it open until the last
> +	 * fd 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 fd, in acquiredFd_. 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::dup(int oldfd, int newfd)
>  {
> +	nonBlockingFds_[newfd] = nonBlockingFds_[oldfd];

If we wanted to be truly compliant I think we would need special
handling here. The kernel has three objects related to files:

- inodes represent files as stored on disk. There's one inode for every
  /dev/video*
- file objects are allocated at open() time and store all data related
  to the open file (including a reference to the inode)
- file descriptors are integers that map to a file

V4L2 handles locking at the file level, so if you dup() and fd, the new
fd will refer to the same file, and should have the same access level as
the original fd.

V4L2CameraProxy is more or less equivalent to the inode, as there's one
instance per camera. Would it make sense to create a V4L2CameraFile
object that would model the file ? It would contain properties
associated with the file (such as the non-blocking flag) and be passed
to V4L2CameraProxy functions instead of the numerical fd. The mapping of
numerical file descriptors to V4L2CameraFile would be handled in
V4L2CompatManager. The V4L2CameraProxy::dup() function would be removed,
as numerical fds wouldn't be visible here. V4L2CameraFile would need to
be refcounted to implement dup(), and so would V4L2CameraProxy to
implement multiple open. I wonder if the refcount could be implemented
through shared_ptr, but that may not be worth it.

>  	refcount_++;
>  }
>  
> -void V4L2CameraProxy::close()
> +void V4L2CameraProxy::close(int fd)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing close";
> +	LOG(V4L2Compat, Debug) << "Servicing close fd = " << fd;
> +
> +	nonBlockingFds_.erase(fd);
> +
> +	if (acquiredFd_ == fd)
> +		acquiredFd_ = -1;

Should this be replaced by a call to unlock() to unbind fd from vcam ?

>  
>  	if (--refcount_ > 0)
>  		return;
>  
>  	vcam_->close();
> +
> +	initialized_ = false;
>  }
>  
>  void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
> @@ -220,9 +243,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(int fd, struct v4l2_fmtdesc *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt fd = " << fd;
> +
>  
>  	if (!validateBufferType(arg->type) ||
>  	    arg->index >= streamConfig_.formats().pixelformats().size())
> @@ -236,9 +260,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(int fd, struct v4l2_format *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt fd = " << fd;
> +
>  
>  	if (!validateBufferType(arg->type))
>  		return -EINVAL;
> @@ -274,9 +299,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(int fd, struct v4l2_format *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt fd = " << fd;
> +
> +	int ret = lock(fd);
> +	if (ret < 0)
> +		return ret;
>  
>  	if (!validateBufferType(arg->type))
>  		return -EINVAL;
> @@ -284,9 +313,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;
>  
> @@ -301,9 +330,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(int fd, struct v4l2_format *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt fd = " << fd;
>  
>  	if (!validateBufferType(arg->type))
>  		return -EINVAL;
> @@ -328,11 +357,14 @@ int V4L2CameraProxy::freeBuffers()
>  	return 0;
>  }
>  
> -int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
> +int V4L2CameraProxy::vidioc_reqbufs(int fd, struct v4l2_requestbuffers *arg)
>  {
> -	int ret;
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs fd = " << fd;
> +
>  
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs";
> +	int ret = lock(fd);
> +	if (ret < 0)
> +		return ret;
>  
>  	if (!validateBufferType(arg->type) ||
>  	    !validateMemoryType(arg->memory))
> @@ -341,9 +373,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(fd);
>  		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,
> @@ -386,6 +430,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;
>  	}
> @@ -395,9 +440,13 @@ int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
>  	return 0;
>  }
>  
> -int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
> +int V4L2CameraProxy::vidioc_querybuf(int fd, struct v4l2_buffer *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf fd = " << fd;
> +
> +	int ret = lock(fd);
> +	if (ret < 0)
> +		return ret;
>  
>  	if (!validateBufferType(arg->type) ||
>  	    arg->index >= bufferCount_)
> @@ -410,17 +459,21 @@ int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
>  	return 0;
>  }
>  
> -int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
> +int V4L2CameraProxy::vidioc_qbuf(int fd, struct v4l2_buffer *arg)
>  {
>  	LOG(V4L2Compat, Debug) << "Servicing vidioc_qbuf, index = "
> -			       << arg->index;
> +			       << arg->index << " fd = " << fd;
> +
> +	int ret = lock(fd);
> +	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;
>  
> @@ -430,15 +483,19 @@ int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
>  	return ret;
>  }
>  
> -int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)
> +int V4L2CameraProxy::vidioc_dqbuf(int fd, struct v4l2_buffer *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf fd = " << fd;
> +
> +	int ret = lock(fd);
> +	if (ret < 0)
> +		return ret;
>  
>  	if (!validateBufferType(arg->type) ||
>  	    !validateMemoryType(arg->memory))
>  		return -EINVAL;
>  
> -	if (!nonBlocking_)
> +	if (!nonBlockingFds_[fd])
>  		vcam_->bufferSema_.acquire();
>  	else if (!vcam_->bufferSema_.tryAcquire())
>  		return -EAGAIN;
> @@ -454,16 +511,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(int fd, int *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon fd = " << fd;
> +
> +	int ret = lock(fd);
> +	if (ret < 0)
> +		return ret;
>  
>  	if (!validateBufferType(*arg))
>  		return -EINVAL;
> @@ -473,14 +534,18 @@ int V4L2CameraProxy::vidioc_streamon(int *arg)
>  	return vcam_->streamOn();
>  }
>  
> -int V4L2CameraProxy::vidioc_streamoff(int *arg)
> +int V4L2CameraProxy::vidioc_streamoff(int fd, int *arg)
>  {
> -	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff";
> +	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff fd = " << fd;
> +
> +	int ret = lock(fd);
> +	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);
> @@ -488,7 +553,7 @@ int V4L2CameraProxy::vidioc_streamoff(int *arg)
>  	return ret;
>  }
>  
> -int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
> +int V4L2CameraProxy::ioctl(int fd, unsigned long request, void *arg)
>  {
>  	int ret;
>  	switch (request) {
> @@ -496,34 +561,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(fd, 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(fd, 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(fd, 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(fd, static_cast<struct v4l2_format *>(arg));
>  		break;
>  	case VIDIOC_REQBUFS:
> -		ret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));
> +		ret = vidioc_reqbufs(fd, static_cast<struct v4l2_requestbuffers *>(arg));
>  		break;
>  	case VIDIOC_QUERYBUF:
> -		ret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));
> +		ret = vidioc_querybuf(fd, static_cast<struct v4l2_buffer *>(arg));
>  		break;
>  	case VIDIOC_QBUF:
> -		ret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));
> +		ret = vidioc_qbuf(fd, static_cast<struct v4l2_buffer *>(arg));
>  		break;
>  	case VIDIOC_DQBUF:
> -		ret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));
> +		ret = vidioc_dqbuf(fd, static_cast<struct v4l2_buffer *>(arg));
>  		break;
>  	case VIDIOC_STREAMON:
> -		ret = vidioc_streamon(static_cast<int *>(arg));
> +		ret = vidioc_streamon(fd, static_cast<int *>(arg));
>  		break;
>  	case VIDIOC_STREAMOFF:
> -		ret = vidioc_streamoff(static_cast<int *>(arg));
> +		ret = vidioc_streamoff(fd, static_cast<int *>(arg));
>  		break;
>  	default:
>  		ret = -ENOTTY;
> @@ -538,10 +603,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(int fd)
>  {
> +	if (acquiredFd_ == fd)
> +		return 0;
> +
> +	if (acquiredFd_ >= 0)
> +		return -EBUSY;
> +
>  	efd_ = fd;
>  	vcam_->bind(fd);
> +
> +	acquiredFd_ = fd;
> +
> +	return 0;
> +}
> +
> +void V4L2CameraProxy::unlock(int fd)
> +{
> +	if (acquiredFd_ != fd)
> +		return;
> +
> +	efd_ = -1;
> +	vcam_->unbind();
> +
> +	acquiredFd_ = -1;
>  }
>  
>  struct PixelFormatPlaneInfo {
> diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
> index b5a9801..90e518c 100644
> --- a/src/v4l2/v4l2_camera_proxy.h
> +++ b/src/v4l2/v4l2_camera_proxy.h
> @@ -26,15 +26,13 @@ class V4L2CameraProxy
>  public:
>  	V4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);
>  
> -	int open(bool nonBlocking);
> -	void dup();
> -	void close();
> +	int open(int fd, bool nonBlocking);
> +	void dup(int oldfd, int newfd);
> +	void close(int fd);
>  	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(int fd, unsigned long request, void *arg);
>  
>  private:
>  	bool validateBufferType(uint32_t type);
> @@ -47,16 +45,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(int fd, struct v4l2_fmtdesc *arg);
> +	int vidioc_g_fmt(int fd, struct v4l2_format *arg);
> +	int vidioc_s_fmt(int fd, struct v4l2_format *arg);
> +	int vidioc_try_fmt(int fd, struct v4l2_format *arg);
> +	int vidioc_reqbufs(int fd, struct v4l2_requestbuffers *arg);
> +	int vidioc_querybuf(int fd, struct v4l2_buffer *arg);
> +	int vidioc_qbuf(int fd, struct v4l2_buffer *arg);
> +	int vidioc_dqbuf(int fd, struct v4l2_buffer *arg);
> +	int vidioc_streamon(int fd, int *arg);
> +	int vidioc_streamoff(int fd, int *arg);
> +
> +	int lock(int fd);
> +	void unlock(int fd);
>  
>  	static unsigned int bplMultiplier(uint32_t format);
>  	static unsigned int imageSize(uint32_t format, unsigned int width,
> @@ -67,7 +68,6 @@ private:
>  
>  	unsigned int refcount_;
>  	unsigned int index_;
> -	bool nonBlocking_;
>  
>  	struct v4l2_format curV4L2Format_;
>  	StreamConfiguration streamConfig_;
> @@ -82,6 +82,19 @@ private:
>  	std::unique_ptr<V4L2Camera> vcam_;
>  
>  	int efd_;
> +
> +	std::map<int, bool> nonBlockingFds_;
> +	/*
> +	 * 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).
> +	 */
> +	int acquiredFd_;
> +
> +	bool initialized_;
>  };
>  
>  #endif /* __V4L2_CAMERA_PROXY_H__ */
> diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
> index 9298c0f..ce1eace 100644
> --- a/src/v4l2/v4l2_compat_manager.cpp
> +++ b/src/v4l2/v4l2_compat_manager.cpp
> @@ -148,24 +148,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);
> +	unsigned int camera_index = static_cast<unsigned int>(ret);
> +
> +	V4L2CameraProxy *proxy = proxies_[camera_index].get();
> +	proxy->open(efd, oflag & O_NONBLOCK);
> +
>  	devices_.emplace(efd, proxy);
>  
>  	return efd;
> @@ -181,7 +174,7 @@ int V4L2CompatManager::dup(int oldfd)
>  	if (device != devices_.end()) {
>  		V4L2CameraProxy *proxy = device->second;
>  		devices_[newfd] = proxy;
> -		proxy->dup();
> +		proxy->dup(oldfd, newfd);
>  	}
>  
>  	return newfd;
> @@ -191,7 +184,7 @@ int V4L2CompatManager::close(int fd)
>  {
>  	V4L2CameraProxy *proxy = getProxy(fd);
>  	if (proxy) {
> -		proxy->close();
> +		proxy->close(fd);
>  		devices_.erase(fd);
>  		return 0;
>  	}
> @@ -237,5 +230,5 @@ int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)
>  	if (!proxy)
>  		return fops_.ioctl(fd, request, arg);
>  
> -	return proxy->ioctl(request, arg);
> +	return proxy->ioctl(fd, request, arg);
>  }

Patch

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_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 13db428..594dd13 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -33,45 +33,68 @@  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),
+	  acquiredFd_(-1), initialized_(false)
 {
 	querycap(camera);
 }
 
-int V4L2CameraProxy::open(bool nonBlocking)
+int V4L2CameraProxy::open(int fd, bool nonBlocking)
 {
-	LOG(V4L2Compat, Debug) << "Servicing open";
+	LOG(V4L2Compat, Debug) << "Servicing open fd = " << fd;
 
-	int ret = vcam_->open();
-	if (ret < 0) {
-		errno = -ret;
-		return -1;
-	}
+	nonBlockingFds_[fd] = nonBlocking;
 
-	nonBlocking_ = nonBlocking;
+	refcount_++;
+
+	if (initialized_)
+		return 0;
+
+	/*
+	 * We will open the camera here, once, and keep it open until the last
+	 * fd 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 fd, in acquiredFd_. 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::dup(int oldfd, int newfd)
 {
+	nonBlockingFds_[newfd] = nonBlockingFds_[oldfd];
 	refcount_++;
 }
 
-void V4L2CameraProxy::close()
+void V4L2CameraProxy::close(int fd)
 {
-	LOG(V4L2Compat, Debug) << "Servicing close";
+	LOG(V4L2Compat, Debug) << "Servicing close fd = " << fd;
+
+	nonBlockingFds_.erase(fd);
+
+	if (acquiredFd_ == fd)
+		acquiredFd_ = -1;
 
 	if (--refcount_ > 0)
 		return;
 
 	vcam_->close();
+
+	initialized_ = false;
 }
 
 void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
@@ -220,9 +243,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(int fd, struct v4l2_fmtdesc *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_enum_fmt fd = " << fd;
+
 
 	if (!validateBufferType(arg->type) ||
 	    arg->index >= streamConfig_.formats().pixelformats().size())
@@ -236,9 +260,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(int fd, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_g_fmt fd = " << fd;
+
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -274,9 +299,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(int fd, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_s_fmt fd = " << fd;
+
+	int ret = lock(fd);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -284,9 +313,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;
 
@@ -301,9 +330,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(int fd, struct v4l2_format *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_try_fmt fd = " << fd;
 
 	if (!validateBufferType(arg->type))
 		return -EINVAL;
@@ -328,11 +357,14 @@  int V4L2CameraProxy::freeBuffers()
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
+int V4L2CameraProxy::vidioc_reqbufs(int fd, struct v4l2_requestbuffers *arg)
 {
-	int ret;
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs fd = " << fd;
+
 
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_reqbufs";
+	int ret = lock(fd);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    !validateMemoryType(arg->memory))
@@ -341,9 +373,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(fd);
 		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,
@@ -386,6 +430,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;
 	}
@@ -395,9 +440,13 @@  int V4L2CameraProxy::vidioc_reqbufs(struct v4l2_requestbuffers *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_querybuf(int fd, struct v4l2_buffer *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_querybuf fd = " << fd;
+
+	int ret = lock(fd);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    arg->index >= bufferCount_)
@@ -410,17 +459,21 @@  int V4L2CameraProxy::vidioc_querybuf(struct v4l2_buffer *arg)
 	return 0;
 }
 
-int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_qbuf(int fd, struct v4l2_buffer *arg)
 {
 	LOG(V4L2Compat, Debug) << "Servicing vidioc_qbuf, index = "
-			       << arg->index;
+			       << arg->index << " fd = " << fd;
+
+	int ret = lock(fd);
+	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;
 
@@ -430,15 +483,19 @@  int V4L2CameraProxy::vidioc_qbuf(struct v4l2_buffer *arg)
 	return ret;
 }
 
-int V4L2CameraProxy::vidioc_dqbuf(struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_dqbuf(int fd, struct v4l2_buffer *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf fd = " << fd;
+
+	int ret = lock(fd);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(arg->type) ||
 	    !validateMemoryType(arg->memory))
 		return -EINVAL;
 
-	if (!nonBlocking_)
+	if (!nonBlockingFds_[fd])
 		vcam_->bufferSema_.acquire();
 	else if (!vcam_->bufferSema_.tryAcquire())
 		return -EAGAIN;
@@ -454,16 +511,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(int fd, int *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamon fd = " << fd;
+
+	int ret = lock(fd);
+	if (ret < 0)
+		return ret;
 
 	if (!validateBufferType(*arg))
 		return -EINVAL;
@@ -473,14 +534,18 @@  int V4L2CameraProxy::vidioc_streamon(int *arg)
 	return vcam_->streamOn();
 }
 
-int V4L2CameraProxy::vidioc_streamoff(int *arg)
+int V4L2CameraProxy::vidioc_streamoff(int fd, int *arg)
 {
-	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff";
+	LOG(V4L2Compat, Debug) << "Servicing vidioc_streamoff fd = " << fd;
+
+	int ret = lock(fd);
+	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);
@@ -488,7 +553,7 @@  int V4L2CameraProxy::vidioc_streamoff(int *arg)
 	return ret;
 }
 
-int V4L2CameraProxy::ioctl(unsigned long request, void *arg)
+int V4L2CameraProxy::ioctl(int fd, unsigned long request, void *arg)
 {
 	int ret;
 	switch (request) {
@@ -496,34 +561,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(fd, 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(fd, 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(fd, 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(fd, static_cast<struct v4l2_format *>(arg));
 		break;
 	case VIDIOC_REQBUFS:
-		ret = vidioc_reqbufs(static_cast<struct v4l2_requestbuffers *>(arg));
+		ret = vidioc_reqbufs(fd, static_cast<struct v4l2_requestbuffers *>(arg));
 		break;
 	case VIDIOC_QUERYBUF:
-		ret = vidioc_querybuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_querybuf(fd, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_QBUF:
-		ret = vidioc_qbuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_qbuf(fd, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_DQBUF:
-		ret = vidioc_dqbuf(static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_dqbuf(fd, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_STREAMON:
-		ret = vidioc_streamon(static_cast<int *>(arg));
+		ret = vidioc_streamon(fd, static_cast<int *>(arg));
 		break;
 	case VIDIOC_STREAMOFF:
-		ret = vidioc_streamoff(static_cast<int *>(arg));
+		ret = vidioc_streamoff(fd, static_cast<int *>(arg));
 		break;
 	default:
 		ret = -ENOTTY;
@@ -538,10 +603,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(int fd)
 {
+	if (acquiredFd_ == fd)
+		return 0;
+
+	if (acquiredFd_ >= 0)
+		return -EBUSY;
+
 	efd_ = fd;
 	vcam_->bind(fd);
+
+	acquiredFd_ = fd;
+
+	return 0;
+}
+
+void V4L2CameraProxy::unlock(int fd)
+{
+	if (acquiredFd_ != fd)
+		return;
+
+	efd_ = -1;
+	vcam_->unbind();
+
+	acquiredFd_ = -1;
 }
 
 struct PixelFormatPlaneInfo {
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index b5a9801..90e518c 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -26,15 +26,13 @@  class V4L2CameraProxy
 public:
 	V4L2CameraProxy(unsigned int index, std::shared_ptr<Camera> camera);
 
-	int open(bool nonBlocking);
-	void dup();
-	void close();
+	int open(int fd, bool nonBlocking);
+	void dup(int oldfd, int newfd);
+	void close(int fd);
 	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(int fd, unsigned long request, void *arg);
 
 private:
 	bool validateBufferType(uint32_t type);
@@ -47,16 +45,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(int fd, struct v4l2_fmtdesc *arg);
+	int vidioc_g_fmt(int fd, struct v4l2_format *arg);
+	int vidioc_s_fmt(int fd, struct v4l2_format *arg);
+	int vidioc_try_fmt(int fd, struct v4l2_format *arg);
+	int vidioc_reqbufs(int fd, struct v4l2_requestbuffers *arg);
+	int vidioc_querybuf(int fd, struct v4l2_buffer *arg);
+	int vidioc_qbuf(int fd, struct v4l2_buffer *arg);
+	int vidioc_dqbuf(int fd, struct v4l2_buffer *arg);
+	int vidioc_streamon(int fd, int *arg);
+	int vidioc_streamoff(int fd, int *arg);
+
+	int lock(int fd);
+	void unlock(int fd);
 
 	static unsigned int bplMultiplier(uint32_t format);
 	static unsigned int imageSize(uint32_t format, unsigned int width,
@@ -67,7 +68,6 @@  private:
 
 	unsigned int refcount_;
 	unsigned int index_;
-	bool nonBlocking_;
 
 	struct v4l2_format curV4L2Format_;
 	StreamConfiguration streamConfig_;
@@ -82,6 +82,19 @@  private:
 	std::unique_ptr<V4L2Camera> vcam_;
 
 	int efd_;
+
+	std::map<int, bool> nonBlockingFds_;
+	/*
+	 * 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).
+	 */
+	int acquiredFd_;
+
+	bool initialized_;
 };
 
 #endif /* __V4L2_CAMERA_PROXY_H__ */
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 9298c0f..ce1eace 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -148,24 +148,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);
+	unsigned int camera_index = static_cast<unsigned int>(ret);
+
+	V4L2CameraProxy *proxy = proxies_[camera_index].get();
+	proxy->open(efd, oflag & O_NONBLOCK);
+
 	devices_.emplace(efd, proxy);
 
 	return efd;
@@ -181,7 +174,7 @@  int V4L2CompatManager::dup(int oldfd)
 	if (device != devices_.end()) {
 		V4L2CameraProxy *proxy = device->second;
 		devices_[newfd] = proxy;
-		proxy->dup();
+		proxy->dup(oldfd, newfd);
 	}
 
 	return newfd;
@@ -191,7 +184,7 @@  int V4L2CompatManager::close(int fd)
 {
 	V4L2CameraProxy *proxy = getProxy(fd);
 	if (proxy) {
-		proxy->close();
+		proxy->close(fd);
 		devices_.erase(fd);
 		return 0;
 	}
@@ -237,5 +230,5 @@  int V4L2CompatManager::ioctl(int fd, unsigned long request, void *arg)
 	if (!proxy)
 		return fops_.ioctl(fd, request, arg);
 
-	return proxy->ioctl(request, arg);
+	return proxy->ioctl(fd, request, arg);
 }