diff options
-rw-r--r-- | src/libcamera/include/ipc_unixsocket.h | 57 | ||||
-rw-r--r-- | src/libcamera/ipc_unixsocket.cpp | 309 | ||||
-rw-r--r-- | src/libcamera/meson.build | 2 |
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', |