[libcamera-devel] [RFC PATCH 06/17] IPA: IPC: add IPAIPC

Paul Elder paul.elder at ideasonboard.com
Wed Aug 26 13:09:15 CEST 2020


This patch adds the abstract IPAIPC class and an implementation that uses
UnixSocket, IPAIPCUnixSocket.

IPAIPC exposes two functions to send a message synchronously, for IPA
setup calls such as init(), mapBuffers(), configure(), etc, and to send
a message asynchronously, for streaming calls such as processEvent().
It also exposes a signal for callbacks. Just like the callforwards, the
IPAIPC has no knowledge of the individual IPA functions, therefore it is
the responsibililty of the specific IPA proxy to direct the callback to
the specific callback handler in the pipeline handler. An IPAIPCFactory
and REGISTER_IPA_IPC facility is also implemented, similar to how
pipeline handlers and IPA proxies are declared and created.

IPAIPCUnixSocket is an implementation of IPAIPC that uses UnixSocket.

For now, IPAProxyRPi fetches IPAIPCUnixSocket and uses it, but this can
be changed so that the IPAManager fetches it based on some setting
somewhere, and feeds the IPAIPC to the proxy.

Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
---
 include/libcamera/internal/ipa_ipc.h          |  73 ++++++++
 .../libcamera/internal/ipa_ipc_unixsocket.h   | 116 ++++++++++++
 src/libcamera/ipa_ipc.cpp                     |  58 ++++++
 src/libcamera/ipa_ipc_unixsocket.cpp          | 177 ++++++++++++++++++
 4 files changed, 424 insertions(+)
 create mode 100644 include/libcamera/internal/ipa_ipc.h
 create mode 100644 include/libcamera/internal/ipa_ipc_unixsocket.h
 create mode 100644 src/libcamera/ipa_ipc.cpp
 create mode 100644 src/libcamera/ipa_ipc_unixsocket.cpp

diff --git a/include/libcamera/internal/ipa_ipc.h b/include/libcamera/internal/ipa_ipc.h
new file mode 100644
index 00000000..bd7b9bf2
--- /dev/null
+++ b/include/libcamera/internal/ipa_ipc.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_ipc.h - Image Processing Algorithm IPC module for IPA proxies
+ */
+#ifndef __LIBCAMERA_INTERNAL_IPA_IPC_H__
+#define __LIBCAMERA_INTERNAL_IPA_IPC_H__
+
+#include <vector>
+
+namespace libcamera {
+
+class IPAIPC
+{
+public:
+	IPAIPC([[maybe_unused]] const char *ipa_module_path,
+	       [[maybe_unused]] const char *ipa_proxy_worker_path);
+	virtual ~IPAIPC();
+
+	bool isValid() const { return valid_; }
+
+	virtual int sendSync(uint32_t cmd,
+			     const std::vector<uint8_t> &data_in,
+			     const std::vector<int32_t> &fds_in,
+			     std::vector<uint8_t> *data_out = nullptr,
+			     std::vector<int32_t> *fds_out = nullptr) = 0;
+
+	virtual int sendAsync(uint32_t cmd,
+			      const std::vector<uint8_t> &data_in,
+			      const std::vector<int32_t> &fds_in) = 0;
+
+	Signal<std::vector<uint8_t> &, std::vector<int32_t> &> recvIPC;
+
+protected:
+	bool valid_;
+};
+
+class IPAIPCFactory
+{
+public:
+	IPAIPCFactory(const char *name);
+	virtual ~IPAIPCFactory() {}
+
+	virtual std::unique_ptr<IPAIPC> create(const char *ipa_module_path,
+					       const char *ipa_proxy_worker_path) = 0;
+
+	const std::string &name() const { return name_; }
+
+	static void registerType(IPAIPCFactory *factory);
+	static std::vector<IPAIPCFactory *> &factories();
+
+private:
+	std::string name_;
+};
+
+#define REGISTER_IPA_IPC(ipc)						\
+class ipc##Factory final : public IPAIPCFactory				\
+{									\
+public:									\
+	ipc##Factory() : IPAIPCFactory(#ipc) {}				\
+	std::unique_ptr<IPAIPC> create(const char *ipa_module_path,	\
+				       const char *ipa_proxy_worker_path)\
+	{								\
+		return std::make_unique<ipc>(ipa_module_path,		\
+					     ipa_proxy_worker_path);	\
+	}								\
+};									\
+static ipc##Factory global_##ipc##Factory;
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_INTERNAL_IPA_IPC_H__ */
diff --git a/include/libcamera/internal/ipa_ipc_unixsocket.h b/include/libcamera/internal/ipa_ipc_unixsocket.h
new file mode 100644
index 00000000..4acb27aa
--- /dev/null
+++ b/include/libcamera/internal/ipa_ipc_unixsocket.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_ipc_unixsocket.h - Image Processing Algorithm IPC module using unix socket
+ */
+#ifndef __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__
+#define __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__
+
+// TODO move this to an ipaipc header directory
+
+#include <vector>
+
+#include <libcamera/span.h>
+
+#include "libcamera/internal/ipa_module.h"
+#include "libcamera/internal/ipc_unixsocket.h"
+
+namespace libcamera {
+
+class Process;
+
+void writeHeader(IPCUnixSocket::Payload &payload, uint32_t cmd, uint32_t seq)
+{
+	uint8_t cmd_arr[] = {static_cast<uint8_t>(cmd & 0xff),
+			     static_cast<uint8_t>(cmd & (0xff << 8)),
+			     static_cast<uint8_t>(cmd & (0xff << 16)),
+			     static_cast<uint8_t>(cmd & (0xff << 24))};
+	uint8_t seq_arr[] = {static_cast<uint8_t>(seq & 0xff),
+			     static_cast<uint8_t>(seq & (0xff << 8)),
+			     static_cast<uint8_t>(seq & (0xff << 16)),
+			     static_cast<uint8_t>(seq & (0xff << 24))};
+	payload.data.insert(payload.data.begin(), cmd_arr, cmd_arr+4);
+	payload.data.insert(payload.data.begin() + 4, seq_arr, seq_arr+4);
+}
+
+std::tuple<uint32_t, uint32_t> readHeader(IPCUnixSocket::Payload &payload)
+{
+	uint32_t cmd = (payload.data[0] & 0xff) |
+		       ((payload.data[1] & 0xff) << 8) |
+		       ((payload.data[2] & 0xff) << 16) |
+		       ((payload.data[3] & 0xff) << 24);
+	uint32_t seq = (payload.data[4] & 0xff) |
+		       ((payload.data[5] & 0xff) << 8) |
+		       ((payload.data[6] & 0xff) << 16) |
+		       ((payload.data[7] & 0xff) << 24);
+
+	return {cmd, seq};
+}
+
+void eraseHeader(IPCUnixSocket::Payload &payload)
+{
+	payload.data.erase(payload.data.begin(), payload.data.begin() + 8);
+}
+
+void writeUInt32(IPCUnixSocket::Payload &payload, uint32_t val, uint32_t pos)
+{
+	if (pos + 4 > payload.data.size())
+		return;
+
+	uint8_t arr[] = {static_cast<uint8_t>(val & 0xff),
+			 static_cast<uint8_t>(val & (0xff << 8)),
+			 static_cast<uint8_t>(val & (0xff << 16)),
+			 static_cast<uint8_t>(val & (0xff << 24))};
+	std::copy(arr, arr + 4, payload.data.begin() + pos);
+}
+
+uint32_t readUInt32(IPCUnixSocket::Payload &payload, uint32_t pos)
+{
+	if (pos + 4 > payload.data.size())
+		return 0;
+
+	return payload.data[pos] & (payload.data[pos + 1] << 8) &
+	       (payload.data[pos + 2] << 16) & (payload.data[pos + 3] << 24);
+}
+
+
+class IPAIPCUnixSocket : public IPAIPC
+{
+public:
+	IPAIPCUnixSocket(const char *ipa_module_path, const char *ipa_proxy_worker_path);
+	~IPAIPCUnixSocket();
+
+	bool isValid() const { return valid_; }
+
+	int sendSync(uint32_t cmd,
+		     const std::vector<uint8_t> &data_in,
+		     const std::vector<int32_t> &fds_in,
+		     std::vector<uint8_t> *data_out,
+		     std::vector<int32_t> *fds_out) override;
+
+	int sendAsync(uint32_t cmd,
+		      const std::vector<uint8_t> &data_in,
+		      const std::vector<int32_t> &fds_in) override;
+
+private:
+	struct CallData {
+		IPCUnixSocket::Payload *response;
+		bool done;
+	};
+
+	void readyRead(IPCUnixSocket *socket);
+	int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response, uint32_t seq);
+
+	uint32_t seq_;
+
+	Process *proc_;
+
+	IPCUnixSocket *socket_;
+
+	std::map<uint32_t, struct CallData> callData_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__ */
diff --git a/src/libcamera/ipa_ipc.cpp b/src/libcamera/ipa_ipc.cpp
new file mode 100644
index 00000000..142f4bc3
--- /dev/null
+++ b/src/libcamera/ipa_ipc.cpp
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_ipc.cpp - Image Processing Algorithm IPC module for IPA proxies
+ */
+
+#include <vector>
+
+#include "libcamera/internal/ipc_unixsocket.h"
+#include "libcamera/internal/log.h"
+#include "libcamera/internal/process.h"
+#include "libcamera/internal/thread.h"
+
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/ipa_ipc.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPAIPC)
+
+IPAIPC::IPAIPC([[maybe_unused]] const char *ipa_module_path,
+	       [[maybe_unused]] const char *ipa_proxy_worker_path)
+	: valid_(false)
+{
+}
+
+IPAIPC::~IPAIPC()
+{
+}
+
+IPAIPCFactory::IPAIPCFactory(const char *name)
+	: name_(name)
+{
+	registerType(this);
+}
+
+void IPAIPCFactory::registerType(IPAIPCFactory *factory)
+{
+	std::vector<IPAIPCFactory *> &factories = IPAIPCFactory::factories();
+
+	factories.push_back(factory);
+
+	LOG(IPAIPC, Debug)
+		<< "Registered IPC \"" << factory->name() << "\"";
+}
+
+std::vector<IPAIPCFactory *> &IPAIPCFactory::factories()
+{
+	static std::vector<IPAIPCFactory *> factories;
+	return factories;
+}
+
+// TODO documentation, obviously
+
+} /* namespace libcamera */
diff --git a/src/libcamera/ipa_ipc_unixsocket.cpp b/src/libcamera/ipa_ipc_unixsocket.cpp
new file mode 100644
index 00000000..aabc81bd
--- /dev/null
+++ b/src/libcamera/ipa_ipc_unixsocket.cpp
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_ipc_unixsocket.cpp - Image Processing Algorithm IPC module using unix socket
+ */
+
+#include <vector>
+
+#include "libcamera/internal/ipc_unixsocket.h"
+#include "libcamera/internal/log.h"
+#include "libcamera/internal/process.h"
+#include "libcamera/internal/thread.h"
+
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/ipa_ipc.h"
+#include "libcamera/internal/ipa_ipc_unixsocket.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPAIPC)
+
+IPAIPCUnixSocket::IPAIPCUnixSocket(const char *ipa_module_path,
+				   const char *ipa_proxy_worker_path)
+	: IPAIPC(ipa_module_path, ipa_proxy_worker_path), seq_(0),
+	  proc_(nullptr), socket_(nullptr)
+{
+	std::vector<int> fds;
+	std::vector<std::string> args;
+	args.push_back(ipa_module_path);
+
+	socket_ = new IPCUnixSocket();
+	int fd = socket_->create();
+	if (fd < 0) {
+		LOG(IPAIPC, Error)
+			<< "Failed to create socket";
+		return;
+	}
+	socket_->readyRead.connect(this, &IPAIPCUnixSocket::readyRead);
+	args.push_back(std::to_string(fd));
+	fds.push_back(fd);
+
+	proc_ = new Process();
+	int ret = proc_->start(ipa_proxy_worker_path, args, fds);
+	if (ret) {
+		LOG(IPAIPC, Error)
+			<< "Failed to start proxy worker process";
+		return;
+	}
+
+	valid_ = true;
+}
+
+IPAIPCUnixSocket::~IPAIPCUnixSocket()
+{
+	delete proc_;
+	delete socket_;
+}
+
+int IPAIPCUnixSocket::sendSync(uint32_t cmd,
+			       const std::vector<uint8_t> &data_in,
+			       const std::vector<int32_t> &fds_in,
+			       std::vector<uint8_t> *data_out,
+			       std::vector<int32_t> *fds_out)
+{
+	IPCUnixSocket::Payload message, response;
+	int ret;
+
+	/* It's fine if seq_ overflows; that'll just be the new epoch. */
+	seq_++;
+	writeHeader(message, cmd, seq_);
+	message.data.insert(message.data.end(), data_in.begin(), data_in.end());
+
+	message.fds = const_cast<std::vector<int32_t> &>(fds_in);
+
+	ret = call(message, &response, seq_);
+	if (ret) {
+		LOG(IPAIPC, Error) << "Failed to call sync";
+		callData_.erase(seq_);
+		return ret;
+	}
+
+	if (data_out)
+		data_out->insert(data_out->end(), response.data.begin(), response.data.end());
+
+	if (fds_out)
+		fds_out->insert(fds_out->end(), response.fds.begin(), response.fds.end());
+
+	return 0;
+}
+
+int IPAIPCUnixSocket::sendAsync(uint32_t cmd,
+				const std::vector<uint8_t> &data_in,
+				const std::vector<int32_t> &fds_in)
+{
+	IPCUnixSocket::Payload message;
+	int ret;
+
+	writeHeader(message, cmd, 0);
+	message.data.insert(message.data.end(), data_in.begin(), data_in.end());
+
+	message.fds = const_cast<std::vector<int32_t> &>(fds_in);
+
+	ret = socket_->send(message);
+	if (ret) {
+		LOG(IPAIPC, Error) << "Failed to call async";
+		return ret;
+	}
+
+	return 0;
+}
+
+void IPAIPCUnixSocket::readyRead(IPCUnixSocket *socket)
+{
+	IPCUnixSocket::Payload message;
+	int ret = socket->receive(&message);
+	if (ret) {
+		LOG(IPAIPC, Error) << "Receive message failed" << ret;
+		return;
+	}
+
+	uint32_t cmd, seq;
+	std::tie(cmd, seq) = readHeader(message);
+
+	auto callData = callData_.find(seq);
+	if (callData != callData_.end()) {
+		eraseHeader(message);
+		/* Is there any way to avoid this copy? */
+		*callData->second.response = message;
+		callData->second.done = true;
+		return;
+	}
+
+	/*
+	 * Received unexpected data, this means it's a call from the IPA.
+	 * We can't return anything to the IPA (gotta keep them under *our*
+	 * control, plus returning would require blocking the caller, and we
+	 * can't afford to do that). Let the proxy do switch-case on cmd.
+	 */
+	recvIPC.emit(message.data, message.fds);
+
+	return;
+}
+
+int IPAIPCUnixSocket::call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response, uint32_t seq)
+{
+	Timer timeout;
+	int ret;
+
+	callData_[seq].response = response;
+	callData_[seq].done = false;
+
+	ret = socket_->send(message);
+	if (ret)
+		return ret;
+
+	timeout.start(200);
+	while (!callData_[seq].done) {
+		if (!timeout.isRunning()) {
+			LOG(IPAIPC, Error) << "Call timeout!";
+			callData_.erase(seq);
+			return -ETIMEDOUT;
+		}
+
+		Thread::current()->eventDispatcher()->processEvents();
+	}
+
+	callData_.erase(seq);
+
+	return 0;
+}
+
+REGISTER_IPA_IPC(IPAIPCUnixSocket)
+
+} /* namespace libcamera */
-- 
2.27.0



More information about the libcamera-devel mailing list