[libcamera-devel] [PATCH v3 5/5] tests: Introduce hotplug hot-unplug unit test

Umang Jain email at uajain.com
Wed Jun 10 16:42:56 CEST 2020


Hi Laurent,

On 6/8/20 4:24 AM, Laurent Pinchart wrote:
> Hi Umang,
>
> On Mon, Jun 08, 2020 at 01:24:37AM +0300, Laurent Pinchart wrote:
>> On Thu, May 21, 2020 at 01:54:27PM +0000, Umang Jain wrote:
>>> This test checks the code-paths for camera's hotplugged and
>>> unplugged support. It is based off bind/unbind of a UVC device
>> s/off/on/
>>
>>> from sysfs. Hence, this tests required root permissions to run
>> s/tests required/test requires/
>>
>>> and should have atleast one already bound UVC device present
>> s/atleast/at least/
>>
>>> on the system.
>> s/on/in/
>>
>>> Signed-off-by: Umang Jain <email at uajain.com>
>>> ---
>>>   test/hotplug-cameras.cpp | 153 +++++++++++++++++++++++++++++++++++++++
>>>   test/meson.build         |   1 +
>>>   2 files changed, 154 insertions(+)
>>>   create mode 100644 test/hotplug-cameras.cpp
>>>
>>> diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp
>>> new file mode 100644
>>> index 0000000..72c370f
>>> --- /dev/null
>>> +++ b/test/hotplug-cameras.cpp
>>> @@ -0,0 +1,153 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>>> +/*
>>> + * Copyright (C) 2020, Umang Jain <email at uajain.com>
>>> + *
>>> + * hotplug-cameras.cpp - Emulate cameraAdded/cameraRemoved signals in CameraManager
>> Maybe s/Emulate/Test/ ?
>>
>>> + */
>>> +
>>> +#include <dirent.h>
>>> +#include <fstream>
>>> +#include <iostream>
>>> +#include <string.h>
>>> +#include <unistd.h>
>>> +
>> No need for a blank line here, and you can then merge the two groups of
>> headers in alphabetical order.
>>
>>> +#include <fcntl.h>
>>> +#include <sys/stat.h>
>>> +#include <sys/types.h>
>>> +
>>> +#include <libcamera/camera.h>
>>> +#include <libcamera/camera_manager.h>
>>> +#include <libcamera/event_dispatcher.h>
>>> +#include <libcamera/timer.h>
>>> +
>>> +#include "libcamera/internal/file.h"
>>> +#include "libcamera/internal/thread.h"
>>> +
>>> +#include "test.h"
>>> +
>>> +using namespace std;
>> As you use the std namespace explicitly below, you can drop this line.
>>
>>> +using namespace libcamera;
>>> +
>>> +class HotplugTest : public Test
>>> +{
>>> +protected:
>>> +	void cameraAddedHandler(std::shared_ptr<Camera> cam)
>>> +	{
>>> +		cameraAddedPass_ = true;
>>> +	}
>>> +
>>> +	void cameraRemovedHandler(std::shared_ptr<Camera> cam)
>>> +	{
>>> +		cameraRemovedPass_ = true;
>>> +	}
>>> +
>>> +	int init()
>>> +	{
>>> +		if (!File::exists("/sys/module/uvcvideo")) {
>>> +			std::cout << "uvcvideo driver is not loaded, skipping" << std::endl;
>>> +			return TestSkip;
>>> +		}
>>> +
>>> +		if (geteuid() != 0) {
>>> +			std::cout << "This test requires root permissions, skipping" << std::endl;
>>> +			return TestSkip;
>>> +		}
>>> +
>>> +		cm_ = new CameraManager();
>>> +		if (cm_->start()) {
>>> +			std::cout << "Failed to start camera manager" << std::endl;
>>> +			return TestFail;
>>> +		}
>>> +
>>> +		cameraAddedPass_ = false;
>>> +		cameraRemovedPass_ = false;
>>> +
>>> +		cm_->newCameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
>>> +		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);
>>> +
>>> +		uvc_toplevel_ = "/sys/module/uvcvideo/drivers/usb:uvcvideo/";
>> Maybe /sys/bus/usb/drivers/uvcvideo/ to avoid traversing symlinks ? It
>> makes little difference in practice I suppose.
>>
>> s/uvc_toplevel_/uvcTopLevel_/
>>
>> And I would even call this uvcDriverDir to make it more explicit. Now
>> that I think about it, as uvc_toplevel_ is constant, maybe we should
>> have
>>
>> 	static const std::string uvcDriverDir_;
>>
>> as a static class member.
>>
>>> +
>>> +		return 0;
>>> +	}
>>> +
>>> +	int run()
>>> +	{
>>> +		DIR *dir;
>>> +		struct dirent *dirent, *dirent2;
>>> +		std::string uvc_driver_dir;
>>> +		bool uvc_driver_found = false;
>> camelCase please :-)
>>
>>> +
>>> +		dir = opendir(uvc_toplevel_.c_str());
>>> +		/* Find a UVC device driver symlink, which we can bind/unbind */
>> s/unbind/unbind./ (same for the other comments below)
>>
>>> +		while ((dirent = readdir(dir)) != nullptr) {
>>> +			if (dirent->d_type != DT_LNK)
>>> +				continue;
>>> +
>>> +			std::string child_dir = uvc_toplevel_ + dirent->d_name;
>>> +			DIR *device_driver = opendir(child_dir.c_str());
>>> +			while ((dirent2 = readdir(device_driver)) != nullptr) {
>>> +				if (strncmp(dirent2->d_name, "video4linux", 11) == 0) {
>>> +					uvc_driver_dir = dirent->d_name;
>>> +					uvc_driver_found = true;
>>> +					break;
>>> +				}
>>> +			}
>>> +			closedir(device_driver);
>>> +
>>> +			if (uvc_driver_found)
>>> +				break;
>> Can't this be simplified with
>>
>> 			std::string child_dir = uvc_toplevel_ + dirent->d_name;
>> 			if (!File::exist(uvc_toplevel_ + dirent->d_name + "/video4linux"))
>> 				continue;
>>
>> 			uvc_driver_dir = dirent->d_name;
>> 			break;
>>
>>> +		}
>>> +		closedir(dir);
>>> +
>>> +		/* If no UVC driver found, skip */
>>> +		if (!uvc_driver_found)
>> And here,
>>
>> 		if (uvc_driver_dir.empty())
>>
>> to remove uvc_driver_found.
>>
>>> +			return TestSkip;
>>> +
>>> +		/* Unbind a camera, process events */
>>> +		int fd1 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/unbind", O_WRONLY);
>> Maybe replacing the hardcoded string with uvc_toplevel_ + "unbind" ?
>> This would become
>>
>> 		int fd1 = open((uvcDriverDir_ + "unbind").c_str(), O_WRONLY);
>>
>>> +		write(fd1, uvc_driver_dir.c_str(), uvc_driver_dir.size());
>>> +		close(fd1);
>> Blank line here ?
>>
>> I wonder if using the C++ file API would be simpler here.
>>
>> 		std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
>> 			<< uvc_driver_dir;;
>>
>> and that's it.
>>
>>> +		Timer timer;
>>> +		timer.start(1000);
>>> +		while (timer.isRunning())
>> 		while (timer.isRunning() && !cameraRemovedPass_)
>>
>> to shorten the wait time. Same below.
>>
>>> +			Thread::current()->eventDispatcher()->processEvents();
>>> +
>> How about already testing for cameraRemovedPass_ here ?
>>
>> 		if (!cameraRemovedPass_)
>> 			return TestFail;
>>
>>> +		/* \todo: Fix this workaround of stopping and starting the camera-manager.
>>> +		 *  We need to do this, so that cm_ release all references to the uvc media symlinks.
>>> +		 */
>> We can merge the series without fixing this, but I think it needs to be
>> handled sooner than latter.
> I've had a quick look, and this seems to be caused by the pipeline
> handler never getting deleted on hot-unplug. This is due to a reference
> to the pipeline handler being stored in the camera manager and never
> dropped.
>
> I'm not sure we actually need to store those references. Can I let you
> investigate ?

I see those references in CameraManager::pipes_ not getting dropped 
anywhere.

Also, I want to understand/handle this situation, in 
CameraManager::Private::init() (on master branch):

                 std::shared_ptr<PipelineHandler> pipe = 
factory->create(cm_);

pipe has use_count() of 2 (I checked); and a bit below:

                 pipes_.push_back(std::move(pipe));

pipes_ vector gets a created PipelineHander at refcount 2. Now, I can 
drop "a" reference

to this pipe that got added in pipes_ in maybe say, 
CameraManager::Private::removeCamera() on hot-unplug.

Well, even then it will retain the refcount 1, so still the 
PipelineHandler is not destroyed on hot-unplug,

which might be holding onto the media-device directory I pointed out as 
\todo in the test code.

Do you have any pointers on how can I make the refcount drop to 0, so 
the PipelineHandler in pipes_ can be destroyed?

>
>>> +		cm_->stop();
>>> +		if (cm_->start()) {
>>> +			std::cout << "Failed to restart camera-manager" << std::endl;
>>> +			return TestFail;
>>> +		}
>>> +
>>> +		/* Bind the camera again, process events */
>>> +		int fd2 = open("/sys/module/uvcvideo/drivers/usb:uvcvideo/bind", O_WRONLY);
>> You don't need fd2, you can reuse fd1 (which should be renamed fd).
>>
>>> +		write(fd2, uvc_driver_dir.c_str(), uvc_driver_dir.size());
>>> +		close(fd2);
>>> +
>>> +		timer.start(1000);
>>> +		while (timer.isRunning())
>>> +			Thread::current()->eventDispatcher()->processEvents();
>>> +
>>> +		if (cameraAddedPass_ && cameraRemovedPass_)
>>> +			return TestPass;
>>> +		else
>>> +			return TestFail;
>> This would become
>>
>> 		if (!cameraAddedPass_)
>> 			return TestFail;
>>
>> 		return TestPass;
>>
>>> +	}
>>> +
>>> +	void cleanup()
>>> +	{
>>> +		cm_->stop();
>>> +		delete cm_;
>>> +	}
>>> +
>>> +private:
>>> +	CameraManager *cm_;
>>> +	std::string uvc_toplevel_;
>>> +	bool cameraRemovedPass_;
>> Maybe s/cameraRemovedPass_/cameraRemoved_/ ?
>>
>>> +	bool cameraAddedPass_;
>> Same here.
>>
>>> +};
>>> +
>>> +TEST_REGISTER(HotplugTest)
>>> +
>>> diff --git a/test/meson.build b/test/meson.build
>>> index bd7da14..f7e27d7 100644
>>> --- a/test/meson.build
>>> +++ b/test/meson.build
>>> @@ -31,6 +31,7 @@ internal_tests = [
>>>       ['file',                            'file.cpp'],
>>>       ['file-descriptor',                 'file-descriptor.cpp'],
>>>       ['message',                         'message.cpp'],
>>> +    ['hotplug-cameras',                 'hotplug-cameras.cpp'],
>> Alphabetical order ?
>>
>>>       ['object',                          'object.cpp'],
>>>       ['object-invoke',                   'object-invoke.cpp'],
>>>       ['signal-threads',                  'signal-threads.cpp'],


More information about the libcamera-devel mailing list