summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libcamera/include/ipc_unixsocket.h57
-rw-r--r--src/libcamera/ipc_unixsocket.cpp309
-rw-r--r--src/libcamera/meson.build2
3 files changed, 368 insertions, 0 deletions
diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h
new file mode 100644
index 00000000..ef166d74
--- /dev/null
+++ b/src/libcamera/include/ipc_unixsocket.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipc_unixsocket.h - IPC mechanism based on Unix sockets
+ */
+
+#ifndef __LIBCAMERA_IPC_UNIXSOCKET_H__
+#define __LIBCAMERA_IPC_UNIXSOCKET_H__
+
+#include <cstdint>
+#include <sys/types.h>
+#include <vector>
+
+#include <libcamera/event_notifier.h>
+
+namespace libcamera {
+
+class IPCUnixSocket
+{
+public:
+ struct Payload {
+ std::vector<uint8_t> data;
+ std::vector<int32_t> fds;
+ };
+
+ IPCUnixSocket();
+ ~IPCUnixSocket();
+
+ int create();
+ int bind(int fd);
+ void close();
+ bool isBound() const;
+
+ int send(const Payload &payload);
+ int receive(Payload *payload);
+
+ Signal<IPCUnixSocket *> readyRead;
+
+private:
+ struct Header {
+ uint32_t data;
+ uint8_t fds;
+ };
+
+ int sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num);
+ int recvData(void *buffer, size_t length, int32_t *fds, unsigned int num);
+
+ void dataNotifier(EventNotifier *notifier);
+
+ int fd_;
+ EventNotifier *notifier_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPC_UNIXSOCKET_H__ */
diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
new file mode 100644
index 00000000..c11f1160
--- /dev/null
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets
+ */
+
+#include "ipc_unixsocket.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "log.h"
+
+/**
+ * \file ipc_unixsocket.h
+ * \brief IPC mechanism based on Unix sockets
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPCUnixSocket)
+
+/**
+ * \struct IPCUnixSocket::Payload
+ * \brief Container for an IPC payload
+ *
+ * Holds an array of bytes and an array of file descriptors that can be
+ * transported across a IPC boundary.
+ */
+
+/**
+ * \var IPCUnixSocket::Payload::data
+ * \brief Array of bytes to cross IPC boundary
+ */
+
+/**
+ * \var IPCUnixSocket::Payload::fds
+ * \brief Array of file descriptors to cross IPC boundary
+ */
+
+/**
+ * \class IPCUnixSocket
+ * \brief IPC mechanism based on Unix sockets
+ *
+ * The Unix socket IPC allows bidirectional communication between two processes
+ * through unnamed Unix sockets. It implements datagram-based communication,
+ * transporting entire payloads with guaranteed ordering.
+ *
+ * The IPC design is asynchronous, a message is queued to a receiver which gets
+ * notified that a message is ready to be consumed by a signal. The queuer of
+ * the message gets no notification when a message is delivered nor processed.
+ * If such interactions are needed a protocol specific to the users use-case
+ * should be implemented on top of the IPC objects.
+ *
+ * Establishment of an IPC channel is asymmetrical. The side that initiates
+ * communication first instantiates a local side socket and creates the channel
+ * with create(). The method returns a file descriptor for the remote side of
+ * the channel, which is passed to the remote process through an out-of-band
+ * communication method. The remote side then instantiates a socket, and binds
+ * it to the other side by passing the file descriptor to bind(). At that point
+ * the channel is operation and communication is bidirectional and symmmetrical.
+ */
+
+IPCUnixSocket::IPCUnixSocket()
+ : fd_(-1), notifier_(nullptr)
+{
+}
+
+IPCUnixSocket::~IPCUnixSocket()
+{
+ close();
+}
+
+/**
+ * \brief Create an new IPC channel
+ *
+ * This method creates a new IPC channel. The socket instance is bound to the
+ * local side of the channel, and the method returns a file descriptor bound to
+ * the remote side. The caller is responsible for passing the file descriptor to
+ * the remote process, where it can be used with IPCUnixSocket::bind() to bind
+ * the remote side socket.
+ *
+ * \return A file descriptor on success, negative error code on failure
+ */
+int IPCUnixSocket::create()
+{
+ int sockets[2];
+ int ret;
+
+ ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets);
+ if (ret) {
+ ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to create socket pair: " << strerror(-ret);
+ return ret;
+ }
+
+ ret = bind(sockets[0]);
+ if (ret)
+ return ret;
+
+ return sockets[1];
+}
+
+/**
+ * \brief Bind to an existing IPC channel
+ * \param[in] fd File descriptor
+ *
+ * This method binds the socket instance to an existing IPC channel identified
+ * by the file descriptor \a fd. The file descriptor is obtained from the
+ * IPCUnixSocket::create() method.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int IPCUnixSocket::bind(int fd)
+{
+ if (isBound())
+ return -EINVAL;
+
+ fd_ = fd;
+ notifier_ = new EventNotifier(fd_, EventNotifier::Read);
+ notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier);
+
+ return 0;
+}
+
+/**
+ * \brief Close the IPC channel
+ *
+ * No communication is possible after close() has been called.
+ */
+void IPCUnixSocket::close()
+{
+ if (!isBound())
+ return;
+
+ delete notifier_;
+ notifier_ = nullptr;
+
+ ::close(fd_);
+
+ fd_ = -1;
+}
+
+/**
+ * \brief Check if the IPC channel is bound
+ * \return True if the IPC channel is bound, false otherwise
+ */
+bool IPCUnixSocket::isBound() const
+{
+ return fd_ != -1;
+}
+
+/**
+ * \brief Send a message payload
+ * \param[in] payload Message payload to send
+ *
+ * This method queues the message payload for transmission to the other end of
+ * the IPC channel. It returns immediately, before the message is delivered to
+ * the remote side.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int IPCUnixSocket::send(const Payload &payload)
+{
+ int ret;
+
+ if (!isBound())
+ return -ENOTCONN;
+
+ Header hdr;
+ hdr.data = payload.data.size();
+ hdr.fds = payload.fds.size();
+
+ if (!hdr.data && !hdr.fds)
+ return -EINVAL;
+
+ ret = ::send(fd_, &hdr, sizeof(hdr), 0);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to send: " << strerror(-ret);
+ return ret;
+ }
+
+ return sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds);
+}
+
+/**
+ * \brief Receive a message payload
+ * \param[out] payload Payload where to write the received message
+ *
+ * This method receives the message payload from the IPC channel and writes it
+ * to the \a payload. It blocks until one message is received, if an
+ * asynchronous behavior is desired this method should be called when the
+ * readyRead signal is emitted.
+ *
+ * \todo Add state machine to make sure we don't block forever and that
+ * a header is always followed by a payload.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int IPCUnixSocket::receive(Payload *payload)
+{
+ Header hdr;
+ int ret;
+
+ if (!isBound())
+ return -ENOTCONN;
+
+ if (!payload)
+ return -EINVAL;
+
+ ret = ::recv(fd_, &hdr, sizeof(hdr), 0);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to recv header: " << strerror(-ret);
+ return ret;
+ }
+
+ payload->data.resize(hdr.data);
+ payload->fds.resize(hdr.fds);
+
+ return recvData(payload->data.data(), hdr.data, payload->fds.data(), hdr.fds);
+}
+
+/**
+ * \var IPCUnixSocket::readyRead
+ * \brief A Signal emitted when a message is ready to be read
+ */
+
+int IPCUnixSocket::sendData(const void *buffer, size_t length, const int32_t *fds, unsigned int num)
+{
+ struct iovec iov[1];
+ iov[0].iov_base = const_cast<void *>(buffer);
+ iov[0].iov_len = length;
+
+ char buf[CMSG_SPACE(num * sizeof(uint32_t))];
+ memset(buf, 0, sizeof(buf));
+
+ struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+
+ struct msghdr msg;
+ msg.msg_name = nullptr;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg;
+ msg.msg_controllen = cmsg->cmsg_len;
+ msg.msg_flags = 0;
+ memcpy(CMSG_DATA(cmsg), fds, num * sizeof(uint32_t));
+
+ if (sendmsg(fd_, &msg, 0) < 0) {
+ int ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to sendmsg: " << strerror(-ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int IPCUnixSocket::recvData(void *buffer, size_t length, int32_t *fds, unsigned int num)
+{
+ struct iovec iov[1];
+ iov[0].iov_base = buffer;
+ iov[0].iov_len = length;
+
+ char buf[CMSG_SPACE(num * sizeof(uint32_t))];
+ memset(buf, 0, sizeof(buf));
+
+ struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+
+ struct msghdr msg;
+ msg.msg_name = nullptr;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg;
+ msg.msg_controllen = cmsg->cmsg_len;
+ msg.msg_flags = 0;
+
+ if (recvmsg(fd_, &msg, 0) < 0) {
+ int ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to recvmsg: " << strerror(-ret);
+ return ret;
+ }
+
+ memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t));
+
+ return 0;
+}
+
+void IPCUnixSocket::dataNotifier(EventNotifier *notifier)
+{
+ readyRead.emit(this);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 985aa7e8..45bd9d17 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -13,6 +13,7 @@ libcamera_sources = files([
'ipa_interface.cpp',
'ipa_manager.cpp',
'ipa_module.cpp',
+ 'ipc_unixsocket.cpp',
'log.cpp',
'media_device.cpp',
'media_object.cpp',
@@ -38,6 +39,7 @@ libcamera_headers = files([
'include/formats.h',
'include/ipa_manager.h',
'include/ipa_module.h',
+ 'include/ipc_unixsocket.h',
'include/log.h',
'include/media_device.h',
'include/media_object.h',