/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2019, Google Inc. * * Unix socket IPC test */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; namespace { int calculateLength(int fd) { lseek(fd, 0, 0); int size = lseek(fd, 0, SEEK_END); lseek(fd, 0, 0); return size; } } /* namespace */ 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]); std::vector buf(len); close(fds[i]); if (read(response.fds[0], buf.data(), len) <= 0) return TestFail; if (memcmp(buf.data(), 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); if (ret) return ret; timeout.start(2s); while (!callDone_) { if (!timeout.isRunning()) { cerr << "Call timeout!" << endl; callResponse_ = nullptr; return -ETIMEDOUT; } Thread::current()->eventDispatcher()->processEvents(); } callResponse_ = nullptr; return 0; } void readyRead() { if (!callResponse_) { cerr << "Read ready without expecting data, fail." << endl; return; } if (ipc_.receive(callResponse_)) { cerr << "Receive message failed" << endl; return; } callDone_ = true; } int prepareFDs(IPCUnixSocket::Payload *message, unsigned int num) { int fd = open(self().c_str(), O_RDONLY); if (fd < 0) return fd; int size = 0; for (unsigned int i = 0; i < num; i++) { int clone = dup(fd); if (clone < 0) return clone; size += calculateLength(clone); message->fds.push_back(clone); } close(fd); return size; } pid_t pid_; IPCUnixSocket ipc_; bool callDone_; IPCUnixSocket::Payload *callResponse_; }; /* * Can't use TEST_REGISTER() as single binary needs to act as both proxy * master and slave. */ int main(int argc, char **argv) { if (argc == 2) { UniqueFD ipcfd = UniqueFD(std::stoi(argv[1])); UnixSocketTestSlave slave; return slave.run(std::move(ipcfd)); } UnixSocketTest test; test.setArgs(argc, argv); return test.execute(); } a id='n183' href='#n183'>183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2020, Collabora Ltd.
 *     Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
 *
 * gstlibcameraprovider.c - GStreamer Device Provider
 */

#include "gstlibcameraprovider.h"

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

#include "gstlibcamerasrc.h"
#include "gstlibcamera-utils.h"

using namespace libcamera;

GST_DEBUG_CATEGORY_STATIC(provider_debug);
#define GST_CAT_DEFAULT provider_debug

/**
 * \struct _GstLibcameraDevice
 * \brief libcamera GstDevice implementation
 *
 * This object is used by GstLibcameraProvider to abstract a libcamera
 * device. It also provides helpers to create and configure the
 * libcamerasrc GstElement to be used with this device. The implementation is
 * private to the plugin.
 */

enum {
	PROP_DEVICE_NAME = 1,
};

#define GST_TYPE_LIBCAMERA_DEVICE gst_libcamera_device_get_type()
G_DECLARE_FINAL_TYPE(GstLibcameraDevice, gst_libcamera_device,
		     GST_LIBCAMERA, DEVICE, GstDevice)

struct _GstLibcameraDevice {
	GstDevice parent;
	gchar *name;
};

G_DEFINE_TYPE(GstLibcameraDevice, gst_libcamera_device, GST_TYPE_DEVICE)

static GstElement *
gst_libcamera_device_create_element(GstDevice *device, const gchar *name)
{
	GstElement *source = gst_element_factory_make("libcamerasrc", name);

	/*
	 * Provider and source lives in the same plugin, so making the source
	 * should never fail.
	 */
	g_assert(source);

	g_object_set(source, "camera-name", GST_LIBCAMERA_DEVICE(device)->name, nullptr);

	return source;
}

static gboolean
gst_libcamera_device_reconfigure_element(GstDevice *device,
					 GstElement *element)
{
	if (!GST_LIBCAMERA_IS_SRC(element))
		return FALSE;

	g_object_set(element, "camera-name", GST_LIBCAMERA_DEVICE(device)->name, nullptr);

	return TRUE;
}

static void
gst_libcamera_device_set_property(GObject *object, guint prop_id,
				  const GValue *value, GParamSpec *pspec)
{
	GstLibcameraDevice *device = GST_LIBCAMERA_DEVICE(object);

	switch (prop_id) {
	case PROP_DEVICE_NAME:
		device->name = g_value_dup_string(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
gst_libcamera_device_init([[maybe_unused]] GstLibcameraDevice *self)
{
}

static void
gst_libcamera_device_finalize(GObject *object)
{
	GstLibcameraDevice *self = GST_LIBCAMERA_DEVICE(object);
	gpointer klass = gst_libcamera_device_parent_class;

	g_free(self->name);

	G_OBJECT_CLASS(klass)->finalize(object);
}

static void
gst_libcamera_device_class_init(GstLibcameraDeviceClass *klass)
{
	GstDeviceClass *device_class = GST_DEVICE_CLASS(klass);
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	device_class->create_element = gst_libcamera_device_create_element;
	device_class->reconfigure_element = gst_libcamera_device_reconfigure_element;

	object_class->set_property = gst_libcamera_device_set_property;
	object_class->finalize = gst_libcamera_device_finalize;

	GParamSpec *pspec = g_param_spec_string("name", "Name",
						"The name of the camera device", "",
						(GParamFlags)(G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE |
							      G_PARAM_CONSTRUCT_ONLY));
	g_object_class_install_property(object_class, PROP_DEVICE_NAME, pspec);
}

static GstDevice *
gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)
{
	g_autoptr(GstCaps) caps = gst_caps_new_empty();
	const gchar *name = camera->id().c_str();
	StreamRoles roles;

	roles.push_back(StreamRole::VideoRecording);
	std::unique_ptr<CameraConfiguration> config = camera->generateConfiguration(roles);
	if (!config || config->size() != roles.size()) {
		GST_ERROR("Failed to generate a default configuration for %s", name);
		return nullptr;
	}

	for (const StreamConfiguration &stream_cfg : *config) {
		GstCaps *sub_caps = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
		if (sub_caps)
			gst_caps_append(caps, sub_caps);
	}

	return GST_DEVICE(g_object_new(GST_TYPE_LIBCAMERA_DEVICE,
				       /* \todo Use a unique identifier instead of camera name. */
				       "name", name,
				       "display-name", name,
				       "caps", caps,
				       "device-class", "Source/Video",
				       nullptr));
}

/**
 * \struct _GstLibcameraProvider
 * \brief libcamera GstDeviceProvider implementation
 *
 * This GstFeature is used by GstDeviceMonitor to probe the available
 * libcamera devices. The implementation is private to the plugin.
 */

struct _GstLibcameraProvider {
	GstDeviceProvider parent;
};

G_DEFINE_TYPE_WITH_CODE(GstLibcameraProvider, gst_libcamera_provider,
			GST_TYPE_DEVICE_PROVIDER,
			GST_DEBUG_CATEGORY_INIT(provider_debug, "libcamera-provider", 0,
						"libcamera Device Provider"))

static GList *