[v2,3/3] uvcvideo: Implement acquireDevice() + releaseDevice()
diff mbox series

Message ID 20240827164255.314432-4-hdegoede@redhat.com
State Accepted
Headers show
Series
  • Fix uvcvideo pipelinehandler keeping /dev/video# open
Related show

Commit Message

Hans de Goede Aug. 27, 2024, 4:42 p.m. UTC
The uvcvideo pipeline handler always keeps the uvcvideo /dev/video# node
for a pipeline open after enumerating the camera.

This is a problem for uvcvideo, as keeping the /dev/video# node open stops
the underlying USB device and the USB bus controller from being able to
enter runtime-suspend causing significant unnecessary power-usage.

Implement acquireDevice() + releaseDevice(), openening /dev/video# on
acquire and closing it on release to fix this.

And make validate do a local video_->open() + close() around validate()
when not open yet. To keep validate() working on unacquired cameras.

Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2669
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v2:
- Minor commit msg + code improvements
---
 src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 45 ++++++++++++++++++++
 1 file changed, 45 insertions(+)

Comments

Laurent Pinchart Aug. 29, 2024, 9:58 p.m. UTC | #1
Hi Hans,

Thank you for the patch.

On Tue, Aug 27, 2024 at 06:42:55PM +0200, Hans de Goede wrote:
> The uvcvideo pipeline handler always keeps the uvcvideo /dev/video# node
> for a pipeline open after enumerating the camera.
> 
> This is a problem for uvcvideo, as keeping the /dev/video# node open stops
> the underlying USB device and the USB bus controller from being able to
> enter runtime-suspend causing significant unnecessary power-usage.
> 
> Implement acquireDevice() + releaseDevice(), openening /dev/video# on
> acquire and closing it on release to fix this.
> 
> And make validate do a local video_->open() + close() around validate()
> when not open yet. To keep validate() working on unacquired cameras.

s/yet. To/yet, to/

> Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2669

I'd replace this with

Bug: https://bugs.libcamera.org/show_bug.cgi?id=168

which links to the above pipewire issue.

> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
> Changes in v2:
> - Minor commit msg + code improvements
> ---
>  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 45 ++++++++++++++++++++
>  1 file changed, 45 insertions(+)
> 
> diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> index 8a7409fc..584f5230 100644
> --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> @@ -13,6 +13,7 @@
>  #include <tuple>
>  
>  #include <libcamera/base/log.h>
> +#include <libcamera/base/mutex.h>
>  #include <libcamera/base/utils.h>
>  
>  #include <libcamera/camera.h>
> @@ -48,6 +49,7 @@ public:
>  
>  	const std::string &id() const { return id_; }
>  
> +	Mutex openLock_;
>  	std::unique_ptr<V4L2VideoDevice> video_;
>  	Stream stream_;
>  	std::map<PixelFormat, std::vector<SizeRange>> formats_;
> @@ -93,6 +95,9 @@ private:
>  			   const ControlValue &value);
>  	int processControls(UVCCameraData *data, Request *request);
>  
> +	bool acquireDevice(Camera *camera) override;
> +	void releaseDevice(Camera *camera) override;
> +
>  	UVCCameraData *cameraData(Camera *camera)
>  	{
>  		return static_cast<UVCCameraData *>(camera->_d());
> @@ -107,6 +112,7 @@ UVCCameraConfiguration::UVCCameraConfiguration(UVCCameraData *data)
>  CameraConfiguration::Status UVCCameraConfiguration::validate()
>  {
>  	Status status = Valid;
> +	bool opened = false;

I would move this below.

>  
>  	if (config_.empty())
>  		return Invalid;
> @@ -158,7 +164,23 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
>  	format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
>  	format.size = cfg.size;
>  
> +	/*
> +	 * For power-consumption reasons video_ is closed when the camera
> +	 * is not acquired. Open it here if necessary.
> +	 */

Here.

> +	MutexLocker locker(data_->openLock_);
> +
> +	if (!data_->video_->isOpen()) {
> +		int ret = data_->video_->open();
> +		if (ret)
> +			return Invalid;
> +
> +		opened = true;
> +	}
> +
>  	int ret = data_->video_->tryFormat(&format);
> +	if (opened)
> +		data_->video_->close();
>  	if (ret)
>  		return Invalid;

The code below doesn't need to be protected by the lock.

	bool opened = false;
	int ret;

	MutexLocker locker(data_->openLock_);

	{
		if (!data_->video_->isOpen()) {
			ret = data_->video_->open();
			if (ret)
				return Invalid;

			opened = true;
		}

		ret = data_->video_->tryFormat(&format);
		if (opened)
			data_->video_->close();
	}

	if (ret)
		return Invalid;

>  
> @@ -411,6 +433,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
>  	return true;
>  }
>  
> +bool PipelineHandlerUVC::acquireDevice(Camera *camera)
> +{
> +	UVCCameraData *data = cameraData(camera);
> +
> +	MutexLocker locker(data->openLock_);
> +
> +	return data->video_->open() == 0;
> +}
> +
> +void PipelineHandlerUVC::releaseDevice(Camera *camera)
> +{
> +	UVCCameraData *data = cameraData(camera);
> +
> +	MutexLocker locker(data->openLock_);
> +	data->video_->close();
> +}
> +
>  int UVCCameraData::init(MediaDevice *media)
>  {
>  	int ret;
> @@ -512,6 +551,12 @@ int UVCCameraData::init(MediaDevice *media)
>  
>  	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
>  
> +	/*
> +	 * Close to allow camera to go into runtime-suspend, video_
> +	 * will be re-opened from acquireDevice() and validate().

You can reflow this to 80 columns.

With these small issues addressed,

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

> +	 */
> +	video_->close();
> +
>  	return 0;
>  }
>
Hans de Goede Aug. 30, 2024, 8:01 a.m. UTC | #2
Hi Laurent,

On 8/29/24 11:58 PM, Laurent Pinchart wrote:
> Hi Hans,
> 
> Thank you for the patch.
> 
> On Tue, Aug 27, 2024 at 06:42:55PM +0200, Hans de Goede wrote:
>> The uvcvideo pipeline handler always keeps the uvcvideo /dev/video# node
>> for a pipeline open after enumerating the camera.
>>
>> This is a problem for uvcvideo, as keeping the /dev/video# node open stops
>> the underlying USB device and the USB bus controller from being able to
>> enter runtime-suspend causing significant unnecessary power-usage.
>>
>> Implement acquireDevice() + releaseDevice(), openening /dev/video# on
>> acquire and closing it on release to fix this.
>>
>> And make validate do a local video_->open() + close() around validate()
>> when not open yet. To keep validate() working on unacquired cameras.
> 
> s/yet. To/yet, to/
> 
>> Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2669
> 
> I'd replace this with
> 
> Bug: https://bugs.libcamera.org/show_bug.cgi?id=168
> 
> which links to the above pipewire issue.
> 
>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>> ---
>> Changes in v2:
>> - Minor commit msg + code improvements
>> ---
>>  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 45 ++++++++++++++++++++
>>  1 file changed, 45 insertions(+)
>>
>> diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
>> index 8a7409fc..584f5230 100644
>> --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
>> +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
>> @@ -13,6 +13,7 @@
>>  #include <tuple>
>>  
>>  #include <libcamera/base/log.h>
>> +#include <libcamera/base/mutex.h>
>>  #include <libcamera/base/utils.h>
>>  
>>  #include <libcamera/camera.h>
>> @@ -48,6 +49,7 @@ public:
>>  
>>  	const std::string &id() const { return id_; }
>>  
>> +	Mutex openLock_;
>>  	std::unique_ptr<V4L2VideoDevice> video_;
>>  	Stream stream_;
>>  	std::map<PixelFormat, std::vector<SizeRange>> formats_;
>> @@ -93,6 +95,9 @@ private:
>>  			   const ControlValue &value);
>>  	int processControls(UVCCameraData *data, Request *request);
>>  
>> +	bool acquireDevice(Camera *camera) override;
>> +	void releaseDevice(Camera *camera) override;
>> +
>>  	UVCCameraData *cameraData(Camera *camera)
>>  	{
>>  		return static_cast<UVCCameraData *>(camera->_d());
>> @@ -107,6 +112,7 @@ UVCCameraConfiguration::UVCCameraConfiguration(UVCCameraData *data)
>>  CameraConfiguration::Status UVCCameraConfiguration::validate()
>>  {
>>  	Status status = Valid;
>> +	bool opened = false;
> 
> I would move this below.
> 
>>  
>>  	if (config_.empty())
>>  		return Invalid;
>> @@ -158,7 +164,23 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
>>  	format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
>>  	format.size = cfg.size;
>>  
>> +	/*
>> +	 * For power-consumption reasons video_ is closed when the camera
>> +	 * is not acquired. Open it here if necessary.
>> +	 */
> 
> Here.
> 
>> +	MutexLocker locker(data_->openLock_);
>> +
>> +	if (!data_->video_->isOpen()) {
>> +		int ret = data_->video_->open();
>> +		if (ret)
>> +			return Invalid;
>> +
>> +		opened = true;
>> +	}
>> +
>>  	int ret = data_->video_->tryFormat(&format);
>> +	if (opened)
>> +		data_->video_->close();
>>  	if (ret)
>>  		return Invalid;
> 
> The code below doesn't need to be protected by the lock.
> 
> 	bool opened = false;
> 	int ret;
> 
> 	MutexLocker locker(data_->openLock_);
> 
> 	{
> 		if (!data_->video_->isOpen()) {
> 			ret = data_->video_->open();
> 			if (ret)
> 				return Invalid;
> 
> 			opened = true;
> 		}
> 
> 		ret = data_->video_->tryFormat(&format);
> 		if (opened)
> 			data_->video_->close();
> 	}

I assume you meant to move the "MutexLocker locker(data_->openLock_);"
to inside the { } scope here, so that its lifetime is limited to
that scope ?

> 	if (ret)
> 		return Invalid;

I would prefer to have this inside the { } scope since that is where
it logically belongs and this check will be so fast that it does not
matter wrt holding the lock longer.

With those remarks I agree with your suggestion and I'll implement
this change for v3.

Regards,

Hans




> 
>>  
>> @@ -411,6 +433,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
>>  	return true;
>>  }
>>  
>> +bool PipelineHandlerUVC::acquireDevice(Camera *camera)
>> +{
>> +	UVCCameraData *data = cameraData(camera);
>> +
>> +	MutexLocker locker(data->openLock_);
>> +
>> +	return data->video_->open() == 0;
>> +}
>> +
>> +void PipelineHandlerUVC::releaseDevice(Camera *camera)
>> +{
>> +	UVCCameraData *data = cameraData(camera);
>> +
>> +	MutexLocker locker(data->openLock_);
>> +	data->video_->close();
>> +}
>> +
>>  int UVCCameraData::init(MediaDevice *media)
>>  {
>>  	int ret;
>> @@ -512,6 +551,12 @@ int UVCCameraData::init(MediaDevice *media)
>>  
>>  	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
>>  
>> +	/*
>> +	 * Close to allow camera to go into runtime-suspend, video_
>> +	 * will be re-opened from acquireDevice() and validate().
> 
> You can reflow this to 80 columns.
> 
> With these small issues addressed,
> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
>> +	 */
>> +	video_->close();
>> +
>>  	return 0;
>>  }
>>  
>
Laurent Pinchart Aug. 30, 2024, 8:32 a.m. UTC | #3
On Fri, Aug 30, 2024 at 10:01:01AM +0200, Hans de Goede wrote:
> Hi Laurent,
> 
> On 8/29/24 11:58 PM, Laurent Pinchart wrote:
> > Hi Hans,
> > 
> > Thank you for the patch.
> > 
> > On Tue, Aug 27, 2024 at 06:42:55PM +0200, Hans de Goede wrote:
> >> The uvcvideo pipeline handler always keeps the uvcvideo /dev/video# node
> >> for a pipeline open after enumerating the camera.
> >>
> >> This is a problem for uvcvideo, as keeping the /dev/video# node open stops
> >> the underlying USB device and the USB bus controller from being able to
> >> enter runtime-suspend causing significant unnecessary power-usage.
> >>
> >> Implement acquireDevice() + releaseDevice(), openening /dev/video# on
> >> acquire and closing it on release to fix this.
> >>
> >> And make validate do a local video_->open() + close() around validate()
> >> when not open yet. To keep validate() working on unacquired cameras.
> > 
> > s/yet. To/yet, to/
> > 
> >> Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2669
> > 
> > I'd replace this with
> > 
> > Bug: https://bugs.libcamera.org/show_bug.cgi?id=168
> > 
> > which links to the above pipewire issue.
> > 
> >> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> >> ---
> >> Changes in v2:
> >> - Minor commit msg + code improvements
> >> ---
> >>  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 45 ++++++++++++++++++++
> >>  1 file changed, 45 insertions(+)
> >>
> >> diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> >> index 8a7409fc..584f5230 100644
> >> --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> >> +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> >> @@ -13,6 +13,7 @@
> >>  #include <tuple>
> >>  
> >>  #include <libcamera/base/log.h>
> >> +#include <libcamera/base/mutex.h>
> >>  #include <libcamera/base/utils.h>
> >>  
> >>  #include <libcamera/camera.h>
> >> @@ -48,6 +49,7 @@ public:
> >>  
> >>  	const std::string &id() const { return id_; }
> >>  
> >> +	Mutex openLock_;
> >>  	std::unique_ptr<V4L2VideoDevice> video_;
> >>  	Stream stream_;
> >>  	std::map<PixelFormat, std::vector<SizeRange>> formats_;
> >> @@ -93,6 +95,9 @@ private:
> >>  			   const ControlValue &value);
> >>  	int processControls(UVCCameraData *data, Request *request);
> >>  
> >> +	bool acquireDevice(Camera *camera) override;
> >> +	void releaseDevice(Camera *camera) override;
> >> +
> >>  	UVCCameraData *cameraData(Camera *camera)
> >>  	{
> >>  		return static_cast<UVCCameraData *>(camera->_d());
> >> @@ -107,6 +112,7 @@ UVCCameraConfiguration::UVCCameraConfiguration(UVCCameraData *data)
> >>  CameraConfiguration::Status UVCCameraConfiguration::validate()
> >>  {
> >>  	Status status = Valid;
> >> +	bool opened = false;
> > 
> > I would move this below.
> > 
> >>  
> >>  	if (config_.empty())
> >>  		return Invalid;
> >> @@ -158,7 +164,23 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
> >>  	format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
> >>  	format.size = cfg.size;
> >>  
> >> +	/*
> >> +	 * For power-consumption reasons video_ is closed when the camera
> >> +	 * is not acquired. Open it here if necessary.
> >> +	 */
> > 
> > Here.
> > 
> >> +	MutexLocker locker(data_->openLock_);
> >> +
> >> +	if (!data_->video_->isOpen()) {
> >> +		int ret = data_->video_->open();
> >> +		if (ret)
> >> +			return Invalid;
> >> +
> >> +		opened = true;
> >> +	}
> >> +
> >>  	int ret = data_->video_->tryFormat(&format);
> >> +	if (opened)
> >> +		data_->video_->close();
> >>  	if (ret)
> >>  		return Invalid;
> > 
> > The code below doesn't need to be protected by the lock.
> > 
> > 	bool opened = false;
> > 	int ret;
> > 
> > 	MutexLocker locker(data_->openLock_);
> > 
> > 	{
> > 		if (!data_->video_->isOpen()) {
> > 			ret = data_->video_->open();
> > 			if (ret)
> > 				return Invalid;
> > 
> > 			opened = true;
> > 		}
> > 
> > 		ret = data_->video_->tryFormat(&format);
> > 		if (opened)
> > 			data_->video_->close();
> > 	}
> 
> I assume you meant to move the "MutexLocker locker(data_->openLock_);"
> to inside the { } scope here, so that its lifetime is limited to
> that scope ?

Yes that's what I meant sorry.

> > 	if (ret)
> > 		return Invalid;
> 
> I would prefer to have this inside the { } scope since that is where
> it logically belongs and this check will be so fast that it does not
> matter wrt holding the lock longer.

Fine with me.

> With those remarks I agree with your suggestion and I'll implement
> this change for v3.
> 
> >> @@ -411,6 +433,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
> >>  	return true;
> >>  }
> >>  
> >> +bool PipelineHandlerUVC::acquireDevice(Camera *camera)
> >> +{
> >> +	UVCCameraData *data = cameraData(camera);
> >> +
> >> +	MutexLocker locker(data->openLock_);
> >> +
> >> +	return data->video_->open() == 0;
> >> +}
> >> +
> >> +void PipelineHandlerUVC::releaseDevice(Camera *camera)
> >> +{
> >> +	UVCCameraData *data = cameraData(camera);
> >> +
> >> +	MutexLocker locker(data->openLock_);
> >> +	data->video_->close();
> >> +}
> >> +
> >>  int UVCCameraData::init(MediaDevice *media)
> >>  {
> >>  	int ret;
> >> @@ -512,6 +551,12 @@ int UVCCameraData::init(MediaDevice *media)
> >>  
> >>  	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
> >>  
> >> +	/*
> >> +	 * Close to allow camera to go into runtime-suspend, video_
> >> +	 * will be re-opened from acquireDevice() and validate().
> > 
> > You can reflow this to 80 columns.
> > 
> > With these small issues addressed,
> > 
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > 
> >> +	 */
> >> +	video_->close();
> >> +
> >>  	return 0;
> >>  }
> >>

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index 8a7409fc..584f5230 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -13,6 +13,7 @@ 
 #include <tuple>
 
 #include <libcamera/base/log.h>
+#include <libcamera/base/mutex.h>
 #include <libcamera/base/utils.h>
 
 #include <libcamera/camera.h>
@@ -48,6 +49,7 @@  public:
 
 	const std::string &id() const { return id_; }
 
+	Mutex openLock_;
 	std::unique_ptr<V4L2VideoDevice> video_;
 	Stream stream_;
 	std::map<PixelFormat, std::vector<SizeRange>> formats_;
@@ -93,6 +95,9 @@  private:
 			   const ControlValue &value);
 	int processControls(UVCCameraData *data, Request *request);
 
+	bool acquireDevice(Camera *camera) override;
+	void releaseDevice(Camera *camera) override;
+
 	UVCCameraData *cameraData(Camera *camera)
 	{
 		return static_cast<UVCCameraData *>(camera->_d());
@@ -107,6 +112,7 @@  UVCCameraConfiguration::UVCCameraConfiguration(UVCCameraData *data)
 CameraConfiguration::Status UVCCameraConfiguration::validate()
 {
 	Status status = Valid;
+	bool opened = false;
 
 	if (config_.empty())
 		return Invalid;
@@ -158,7 +164,23 @@  CameraConfiguration::Status UVCCameraConfiguration::validate()
 	format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
 	format.size = cfg.size;
 
+	/*
+	 * For power-consumption reasons video_ is closed when the camera
+	 * is not acquired. Open it here if necessary.
+	 */
+	MutexLocker locker(data_->openLock_);
+
+	if (!data_->video_->isOpen()) {
+		int ret = data_->video_->open();
+		if (ret)
+			return Invalid;
+
+		opened = true;
+	}
+
 	int ret = data_->video_->tryFormat(&format);
+	if (opened)
+		data_->video_->close();
 	if (ret)
 		return Invalid;
 
@@ -411,6 +433,23 @@  bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
 	return true;
 }
 
+bool PipelineHandlerUVC::acquireDevice(Camera *camera)
+{
+	UVCCameraData *data = cameraData(camera);
+
+	MutexLocker locker(data->openLock_);
+
+	return data->video_->open() == 0;
+}
+
+void PipelineHandlerUVC::releaseDevice(Camera *camera)
+{
+	UVCCameraData *data = cameraData(camera);
+
+	MutexLocker locker(data->openLock_);
+	data->video_->close();
+}
+
 int UVCCameraData::init(MediaDevice *media)
 {
 	int ret;
@@ -512,6 +551,12 @@  int UVCCameraData::init(MediaDevice *media)
 
 	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
 
+	/*
+	 * Close to allow camera to go into runtime-suspend, video_
+	 * will be re-opened from acquireDevice() and validate().
+	 */
+	video_->close();
+
 	return 0;
 }