summaryrefslogtreecommitdiff
path: root/test/hotplug-cameras.cpp
blob: 530e9a31120970dee270c4ba8862ac93d5779678 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2020, Umang Jain <email@uajain.com>
 *
 * Test cameraAdded/cameraRemoved signals in CameraManager
 */

#include <dirent.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include <unistd.h>

#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>

#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/file.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>

#include "test.h"

using namespace libcamera;
using namespace std::chrono_literals;

class HotplugTest : public Test
{
protected:
	void cameraAddedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
	{
		cameraAdded_ = true;
	}

	void cameraRemovedHandler([[maybe_unused]] std::shared_ptr<Camera> cam)
	{
		cameraRemoved_ = 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;
		}

		cameraAdded_ = false;
		cameraRemoved_ = false;

		cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler);
		cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler);

		return 0;
	}

	int run()
	{
		DIR *dir;
		struct dirent *dirent;
		std::string uvcDeviceDir;

		dir = opendir(uvcDriverDir_.c_str());
		/* Find a UVC device directory, which we can bind/unbind. */
		while ((dirent = readdir(dir)) != nullptr) {
			if (!File::exists(uvcDriverDir_ + dirent->d_name + "/video4linux"))
				continue;

			uvcDeviceDir = dirent->d_name;
			break;
		}
		closedir(dir);

		/* If no UVC device found, skip the test. */
		if (uvcDeviceDir.empty())
			return TestSkip;

		/* Unbind a camera and process events. */
		std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary)
			<< uvcDeviceDir;
		Timer timer;
		timer.start(1000ms);
		while (timer.isRunning() && !cameraRemoved_)
			Thread::current()->eventDispatcher()->processEvents();
		if (!cameraRemoved_) {
			std::cout << "Camera unplug not detected" << std::endl;
			return TestFail;
		}

		/* Bind the camera again and process events. */
		std::ofstream(uvcDriverDir_ + "bind", std::ios::binary)
			<< uvcDeviceDir;
		timer.start(1000ms);
		while (timer.isRunning() && !cameraAdded_)
			Thread::current()->eventDispatcher()->processEvents();
		if (!cameraAdded_) {
			std::cout << "Camera plug not detected" << std::endl;
			return TestFail;
		}

		return TestPass;
	}

	void cleanup()
	{
		cm_->stop();
		delete cm_;
	}

private:
	CameraManager *cm_;
	static const std::string uvcDriverDir_;
	bool cameraRemoved_;
	bool cameraAdded_;
};

const std::string HotplugTest::uvcDriverDir_ = "/sys/bus/usb/drivers/uvcvideo/";

TEST_REGISTER(HotplugTest)
ass="hl ppc">#include <fcntl.h> #include <iostream> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> #include "libcamera/internal/ipc_unixsocket.h" #include "test.h" #define CMD_CLOSE 0 #define CMD_REVERSE 1 #define CMD_LEN_CALC 2 #define CMD_LEN_CMP 3 #define CMD_JOIN 4 using namespace libcamera; using namespace std; using namespace std::chrono_literals; int calculateLength(int fd) { lseek(fd, 0, 0); int size = lseek(fd, 0, SEEK_END); lseek(fd, 0, 0); return size; } class UnixSocketTestSlave { public: UnixSocketTestSlave() : exitCode_(EXIT_FAILURE), exit_(false) { dispatcher_ = Thread::current()->eventDispatcher(); ipc_.readyRead.connect(this, &UnixSocketTestSlave::readyRead); } int run(UniqueFD fd) { if (ipc_.bind(std::move(fd))) { cerr << "Failed to connect to IPC channel" << endl; return EXIT_FAILURE; } while (!exit_) dispatcher_->processEvents(); ipc_.close(); return exitCode_; } private: void readyRead() { IPCUnixSocket::Payload message, response; int ret; ret = ipc_.receive(&message); if (ret) { cerr << "Receive message failed: " << ret << endl; return; } const uint8_t cmd = message.data[0]; switch (cmd) { case CMD_CLOSE: stop(0); break; case CMD_REVERSE: { response.data = message.data; std::reverse(response.data.begin() + 1, response.data.end()); ret = ipc_.send(response); if (ret < 0) { cerr << "Reverse failed" << endl; stop(ret); } break; } case CMD_LEN_CALC: { int size = 0; for (int fd : message.fds) size += calculateLength(fd); response.data.resize(1 + sizeof(size)); response.data[0] = cmd; memcpy(response.data.data() + 1, &size, sizeof(size)); ret = ipc_.send(response); if (ret < 0) { cerr << "Calc failed" << endl; stop(ret); } break; } case CMD_LEN_CMP: { int size = 0; for (int fd : message.fds) size += calculateLength(fd); int cmp; memcpy(&cmp, message.data.data() + 1, sizeof(cmp)); if (cmp != size) { cerr << "Compare failed" << endl; stop(-ERANGE); } break; } case CMD_JOIN: { int outfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); if (outfd < 0) { cerr << "Create out file failed" << endl; stop(outfd); return; } for (int fd : message.fds) { while (true) { char buf[32]; ssize_t num = read(fd, &buf, sizeof(buf)); if (num < 0) { cerr << "Read failed" << endl; close(outfd); stop(-EIO); return; } else if (!num) break; if (write(outfd, buf, num) < 0) { cerr << "Write failed" << endl; close(outfd); stop(-EIO); return; } } close(fd); } lseek(outfd, 0, 0); response.data.push_back(CMD_JOIN); response.fds.push_back(outfd); ret = ipc_.send(response); if (ret < 0) { cerr << "Join failed" << endl; stop(ret); } close(outfd); break; } default: cerr << "Unknown command " << cmd << endl; stop(-EINVAL); break; } } void stop(int code) { exitCode_ = code; exit_ = true; } IPCUnixSocket ipc_; EventDispatcher *dispatcher_; int exitCode_; bool exit_; }; class UnixSocketTest : public Test { protected: int slaveStart(int fd) { pid_ = fork(); if (pid_ == -1) return TestFail; if (!pid_) { std::string arg = std::to_string(fd); execl(self().c_str(), self().c_str(), arg.c_str(), nullptr); /* Only get here if exec fails. */ exit(TestFail); } return TestPass; } int slaveStop() { int status; if (pid_ < 0) return TestFail; if (waitpid(pid_, &status, 0) < 0) return TestFail; if (!WIFEXITED(status) || WEXITSTATUS(status)) return TestFail; return TestPass; } int testReverse() { IPCUnixSocket::Payload message, response; int ret; message.data = { CMD_REVERSE, 1, 2, 3, 4, 5 }; ret = call(message, &response); if (ret) return ret; std::reverse(response.data.begin() + 1, response.data.end()); if (message.data != response.data) return TestFail; return 0; } int testEmptyFail() { IPCUnixSocket::Payload message; return ipc_.send(message) != -EINVAL; } int testCalc() { IPCUnixSocket::Payload message, response; int sizeOut, sizeIn, ret; sizeOut = prepareFDs(&message, 2); if (sizeOut < 0) return sizeOut; message.data.push_back(CMD_LEN_CALC); ret = call(message, &response); if (ret) return ret; memcpy(&sizeIn, response.data.data() + 1, sizeof(sizeIn)); if (sizeOut != sizeIn) return TestFail; return 0; } int testCmp() { IPCUnixSocket::Payload message; int size; size = prepareFDs(&message, 7); if (size < 0) return size; message.data.resize(1 + sizeof(size)); message.data[0] = CMD_LEN_CMP; memcpy(message.data.data() + 1, &size, sizeof(size)); if (ipc_.send(message)) return TestFail; return 0; } int testFdOrder() { IPCUnixSocket::Payload message, response; int ret; static const char *strings[2] = { "Foo", "Bar", }; int fds[2]; for (unsigned int i = 0; i < std::size(strings); i++) { unsigned int len = strlen(strings[i]); fds[i] = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); if (fds[i] < 0) return TestFail; ret = write(fds[i], strings[i], len); if (ret < 0) return TestFail; lseek(fds[i], 0, 0); message.fds.push_back(fds[i]); } message.data.push_back(CMD_JOIN); ret = call(message, &response); if (ret) return ret; for (unsigned int i = 0; i < std::size(strings); i++) { unsigned int len = strlen(strings[i]); char buf[len]; close(fds[i]); if (read(response.fds[0], &buf, len) <= 0) return TestFail; if (memcmp(buf, strings[i], len)) return TestFail; } close(response.fds[0]); return 0; } int init() { callResponse_ = nullptr; return 0; } int run() { UniqueFD slavefd = ipc_.create(); if (!slavefd.isValid()) return TestFail; if (slaveStart(slavefd.release())) { cerr << "Failed to start slave" << endl; return TestFail; } ipc_.readyRead.connect(this, &UnixSocketTest::readyRead); /* Test reversing a string, this test sending only data. */ if (testReverse()) { cerr << "Reverse array test failed" << endl; return TestFail; } /* Test that an empty message fails. */ if (testEmptyFail()) { cerr << "Empty message test failed" << endl; return TestFail; } /* Test offloading a calculation, this test sending only FDs. */ if (testCalc()) { cerr << "Calc test failed" << endl; return TestFail; } /* Test fire and forget, this tests sending data and FDs. */ if (testCmp()) { cerr << "Cmp test failed" << endl; return TestFail; } /* Test order of file descriptors. */ if (testFdOrder()) { cerr << "fd order test failed" << endl; return TestFail; } /* Close slave connection. */ IPCUnixSocket::Payload close; close.data.push_back(CMD_CLOSE); if (ipc_.send(close)) { cerr << "Closing IPC channel failed" << endl; return TestFail; } ipc_.close(); if (slaveStop()) { cerr << "Failed to stop slave" << endl; return TestFail; } return TestPass; } private: int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response) { Timer timeout; int ret; callDone_ = false; callResponse_ = response; ret = ipc_.send(message);