[libcamera-devel] [PATCH v3 1/2] libcamera: ipc: unix: Add a IPC mechanism based on Unix sockets

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Jul 2 00:09:36 CEST 2019


Hi Niklas,

Thank you for the patch.

On Tue, Jul 02, 2019 at 12:06:11AM +0200, Niklas Söderlund wrote:
> To be able to isolate an IPA component in a separate process an IPC
> mechanism is needed to communicate with it. Add an IPC mechanism based
> on Unix sockets which allows users to pass both data and file descriptors
> to and from the IPA process.
> 
> The implementation allows users to send both data and file descriptors
> in the same message. This allows users to more easily implement
> serialization and deserialization of objects as all elements belonging
> to an object can be sent in one message.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>

Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>

> ---
>  src/libcamera/include/ipc_unixsocket.h |  57 +++++
>  src/libcamera/ipc_unixsocket.cpp       | 309 +++++++++++++++++++++++++
>  src/libcamera/meson.build              |   2 +
>  3 files changed, 368 insertions(+)
>  create mode 100644 src/libcamera/include/ipc_unixsocket.h
>  create mode 100644 src/libcamera/ipc_unixsocket.cpp
> 
> diff --git a/src/libcamera/include/ipc_unixsocket.h b/src/libcamera/include/ipc_unixsocket.h
> new file mode 100644
> index 0000000000000000..ef166d7425549102
> --- /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 0000000000000000..c11f116093c51b0d
> --- /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 985aa7e8ab0eb6ce..45bd9d1793aa0b19 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',

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list