[libcamera-devel,v3,22/22] v4l2: v4l2_camera_proxy: Serialize accesses to the proxy

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

Commit Message

Paul Elder June 23, 2020, 7:08 p.m. UTC
Make the V4L2 compatibility layer thread-safe by serializing accesses to
the V4L2CameraProxy with a lock. Release the lock when blocking for
dqbuf.

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

---
New in v3
---
 src/v4l2/v4l2_camera_proxy.cpp | 21 +++++++++++++++++----
 src/v4l2/v4l2_camera_proxy.h   |  5 ++++-
 2 files changed, 21 insertions(+), 5 deletions(-)

Comments

Laurent Pinchart June 24, 2020, 12:02 a.m. UTC | #1
Hi Paul,

Thank you for the patch.

On Wed, Jun 24, 2020 at 04:08:36AM +0900, Paul Elder wrote:
> Make the V4L2 compatibility layer thread-safe by serializing accesses to
> the V4L2CameraProxy with a lock. Release the lock when blocking for
> dqbuf.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> 
> ---
> New in v3
> ---
>  src/v4l2/v4l2_camera_proxy.cpp | 21 +++++++++++++++++----
>  src/v4l2/v4l2_camera_proxy.h   |  5 ++++-
>  2 files changed, 21 insertions(+), 5 deletions(-)
> 
> diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
> index ed3bcbc..fb3a1fc 100644
> --- a/src/v4l2/v4l2_camera_proxy.cpp
> +++ b/src/v4l2/v4l2_camera_proxy.cpp
> @@ -43,6 +43,8 @@ V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
>  
>  int V4L2CameraProxy::open(V4L2CameraFile *file)
>  {
> +	MutexLocker locker(proxyMutex_);
> +

You could move the lock after the LOG() lines to slightly reduced the
amount of code protected by it.

>  	LOG(V4L2Compat, Debug) << "Servicing open fd = " << file->efd();
>  
>  	files_.insert(file);
> @@ -75,6 +77,8 @@ int V4L2CameraProxy::open(V4L2CameraFile *file)
>  
>  void V4L2CameraProxy::close(V4L2CameraFile *file)
>  {
> +	MutexLocker locker(proxyMutex_);
> +
>  	LOG(V4L2Compat, Debug) << "Servicing close fd = " << file->efd();
>  
>  	files_.erase(file);
> @@ -90,6 +94,8 @@ void V4L2CameraProxy::close(V4L2CameraFile *file)
>  void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
>  			    off64_t offset)
>  {
> +	MutexLocker locker(proxyMutex_);
> +
>  	LOG(V4L2Compat, Debug) << "Servicing mmap";
>  
>  	/* \todo Validate prot and flags properly. */
> @@ -124,6 +130,8 @@ void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
>  
>  int V4L2CameraProxy::munmap(void *addr, size_t length)
>  {
> +	MutexLocker locker(proxyMutex_);
> +
>  	LOG(V4L2Compat, Debug) << "Servicing munmap";
>  
>  	auto iter = mmaps_.find(addr);
> @@ -592,7 +600,8 @@ int V4L2CameraProxy::vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
>  	return ret;
>  }
>  
> -int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
> +int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file,
> +				  struct v4l2_buffer *arg, MutexLocker &locker)

We usually passed parameters that can be modified by pointer, not by
reference.

And you could keep the first two parameters on the first line.

>  {
>  	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf fd = " << file->efd();
>  
> @@ -609,9 +618,11 @@ int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
>  	    !validateMemoryType(arg->memory))
>  		return -EINVAL;
>  
> -	if (!(file->nonBlocking()))
> +	if (!(file->nonBlocking())) {
> +		locker.unlock();
>  		vcam_->waitForBufferAvailable();
> -	else if (!vcam_->isBufferAvailable())
> +		locker.lock();

There's a potential race condition here. waitForBufferAvailable() is
implemented as

void V4L2Camera::waitForBufferAvailable()
{
	MutexLocker locker(bufferMutex_);
        bufferCV_.wait(locker, [&] {
			return bufferAvailableCount_ >= 1 || !isRunning_;
			});
	if (isRunning_)
		bufferAvailableCount_--;
}

and in V4L2Camera::streamOff(), you have

	isRunning_ = false;
	bufferCV_.notify_all();

without taking the bufferMutex_ lock. streamOff() can run in a parallel
thread to waitForBufferAvailable() as you release the locker before
calling waitForBufferAvailable(). Unless I'm mistaken, the notify_all()
call doesn't count as a release operation (in the C++ memory model
sense, see https://people.cs.pitt.edu/~xianeizhang/notes/cpp11_mem.html
for a local resource :-)). The write to isRunning_ could then be visible
to the thread waiting on bufferCV_ only after bufferCV_.notify_all()
gets visible. The wait() would wake up, not see !isRunning_, go back to
sleep, and stay there forever. I initially thought the global lock would
solve this, but after further analysis, I don't think that's the case.

Fortunately, I think the fix should be a simple as

	{
		MutexLocker locker(buferMutex_);
		isRunning_ = false;
	}
	bufferCV_notify_all();

in V4L2Camera::streamOff() in the patch that dropped usage of the
Semaphore class.

I'll let you confirm my analysis :-)

For this patch,

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +	} else if (!vcam_->isBufferAvailable())
>  		return -EAGAIN;
>  
>  	/*
> @@ -706,6 +717,8 @@ const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
>  
>  int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
>  {
> +	MutexLocker locker(proxyMutex_);
> +
>  	if (!arg && (_IOC_DIR(request) & _IOC_WRITE)) {
>  		errno = EFAULT;
>  		return -1;
> @@ -766,7 +779,7 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *ar
>  		ret = vidioc_qbuf(file, static_cast<struct v4l2_buffer *>(arg));
>  		break;
>  	case VIDIOC_DQBUF:
> -		ret = vidioc_dqbuf(file, static_cast<struct v4l2_buffer *>(arg));
> +		ret = vidioc_dqbuf(file, static_cast<struct v4l2_buffer *>(arg), locker);
>  		break;
>  	case VIDIOC_STREAMON:
>  		ret = vidioc_streamon(file, static_cast<int *>(arg));
> diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
> index 041f536..b6d2c2c 100644
> --- a/src/v4l2/v4l2_camera_proxy.h
> +++ b/src/v4l2/v4l2_camera_proxy.h
> @@ -61,7 +61,7 @@ private:
>  	int vidioc_reqbufs(V4L2CameraFile *file, struct v4l2_requestbuffers *arg);
>  	int vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
>  	int vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
> -	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
> +	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg, MutexLocker &locker);
>  	int vidioc_streamon(V4L2CameraFile *file, int *arg);
>  	int vidioc_streamoff(V4L2CameraFile *file, int *arg);
>  
> @@ -105,6 +105,9 @@ private:
>  	 * g/s_priority, enum/g/s_input
>  	 */
>  	V4L2CameraFile *owner_;
> +
> +	/* This mutex is to serialize access to the proxy. */
> +	Mutex proxyMutex_;
>  };
>  
>  #endif /* __V4L2_CAMERA_PROXY_H__ */

Patch

diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index ed3bcbc..fb3a1fc 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -43,6 +43,8 @@  V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
 
 int V4L2CameraProxy::open(V4L2CameraFile *file)
 {
+	MutexLocker locker(proxyMutex_);
+
 	LOG(V4L2Compat, Debug) << "Servicing open fd = " << file->efd();
 
 	files_.insert(file);
@@ -75,6 +77,8 @@  int V4L2CameraProxy::open(V4L2CameraFile *file)
 
 void V4L2CameraProxy::close(V4L2CameraFile *file)
 {
+	MutexLocker locker(proxyMutex_);
+
 	LOG(V4L2Compat, Debug) << "Servicing close fd = " << file->efd();
 
 	files_.erase(file);
@@ -90,6 +94,8 @@  void V4L2CameraProxy::close(V4L2CameraFile *file)
 void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
 			    off64_t offset)
 {
+	MutexLocker locker(proxyMutex_);
+
 	LOG(V4L2Compat, Debug) << "Servicing mmap";
 
 	/* \todo Validate prot and flags properly. */
@@ -124,6 +130,8 @@  void *V4L2CameraProxy::mmap(void *addr, size_t length, int prot, int flags,
 
 int V4L2CameraProxy::munmap(void *addr, size_t length)
 {
+	MutexLocker locker(proxyMutex_);
+
 	LOG(V4L2Compat, Debug) << "Servicing munmap";
 
 	auto iter = mmaps_.find(addr);
@@ -592,7 +600,8 @@  int V4L2CameraProxy::vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
 	return ret;
 }
 
-int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
+int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file,
+				  struct v4l2_buffer *arg, MutexLocker &locker)
 {
 	LOG(V4L2Compat, Debug) << "Servicing vidioc_dqbuf fd = " << file->efd();
 
@@ -609,9 +618,11 @@  int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg)
 	    !validateMemoryType(arg->memory))
 		return -EINVAL;
 
-	if (!(file->nonBlocking()))
+	if (!(file->nonBlocking())) {
+		locker.unlock();
 		vcam_->waitForBufferAvailable();
-	else if (!vcam_->isBufferAvailable())
+		locker.lock();
+	} else if (!vcam_->isBufferAvailable())
 		return -EAGAIN;
 
 	/*
@@ -706,6 +717,8 @@  const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
 
 int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
 {
+	MutexLocker locker(proxyMutex_);
+
 	if (!arg && (_IOC_DIR(request) & _IOC_WRITE)) {
 		errno = EFAULT;
 		return -1;
@@ -766,7 +779,7 @@  int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *ar
 		ret = vidioc_qbuf(file, static_cast<struct v4l2_buffer *>(arg));
 		break;
 	case VIDIOC_DQBUF:
-		ret = vidioc_dqbuf(file, static_cast<struct v4l2_buffer *>(arg));
+		ret = vidioc_dqbuf(file, static_cast<struct v4l2_buffer *>(arg), locker);
 		break;
 	case VIDIOC_STREAMON:
 		ret = vidioc_streamon(file, static_cast<int *>(arg));
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 041f536..b6d2c2c 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -61,7 +61,7 @@  private:
 	int vidioc_reqbufs(V4L2CameraFile *file, struct v4l2_requestbuffers *arg);
 	int vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
 	int vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
-	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
+	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg, MutexLocker &locker);
 	int vidioc_streamon(V4L2CameraFile *file, int *arg);
 	int vidioc_streamoff(V4L2CameraFile *file, int *arg);
 
@@ -105,6 +105,9 @@  private:
 	 * g/s_priority, enum/g/s_input
 	 */
 	V4L2CameraFile *owner_;
+
+	/* This mutex is to serialize access to the proxy. */
+	Mutex proxyMutex_;
 };
 
 #endif /* __V4L2_CAMERA_PROXY_H__ */