From patchwork Thu Jun 27 02:09:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1525 Return-Path: Received: from bin-mail-out-05.binero.net (bin-mail-out-05.binero.net [195.74.38.228]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7D3F46192A for ; Thu, 27 Jun 2019 04:10:10 +0200 (CEST) X-Halon-ID: a53dec57-9880-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id a53dec57-9880-11e9-8ab4-005056917a89; Thu, 27 Jun 2019 04:09:51 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 27 Jun 2019 04:09:54 +0200 Message-Id: <20190627020955.6166-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190627020955.6166-1-niklas.soderlund@ragnatech.se> References: <20190627020955.6166-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 1/2] libcamera: ipc: unix: Add a IPC mechanism based on Unix sockets X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 27 Jun 2019 02:10:10 -0000 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 --- src/libcamera/include/ipc_unixsocket.h | 59 +++++ src/libcamera/ipc_unixsocket.cpp | 288 +++++++++++++++++++++++++ src/libcamera/meson.build | 2 + 3 files changed, 349 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..68edbe72e2af1298 --- /dev/null +++ b/src/libcamera/include/ipc_unixsocket.h @@ -0,0 +1,59 @@ +/* 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 +#include +#include + +#include + +namespace libcamera { + +class IPCUnixSocket +{ +public: + struct Payload { + std::vector data; + std::vector fds; + }; + + IPCUnixSocket(); + + int create(); + int attach(int fd); + void close(); + + Signal payloadReceived; + + int send(Payload &payload); + +private: + struct Header { + uint32_t data; + uint8_t fds; + }; + + int configure(); + + int sendData(void *buffer, size_t length, const int32_t *fds, unsigned int num); + + int recv(); + int recvData(void *buffer, size_t length, int32_t *fds, unsigned int num); + + void dataNotifier(EventNotifier *notifier); + + int fd_; + bool master_; + 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..7b3d8995374dac1e --- /dev/null +++ b/src/libcamera/ipc_unixsocket.cpp @@ -0,0 +1,288 @@ +/* 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 +#include +#include + +#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 IPC mechanism provided by libcamera are centred around passing arrays of + * raw bytes and file descriptors between processes. The primary users of the + * IPC objects are pipeline handlers and Image Processing Algorithms components. + * A pipeline handler would act as an IPC master and send messages for an IPA to + * process and react to. + * + * 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 delivers nor processed. + * If such interactions are needed a protocol specific to the users use-case + * should be implemented on top of the IPC objects. + * + * The IPC design can transmit messages in any direction and the only difference + * from a master and slave operation is how they are created and attached to one + * another. After the two parts are setup the operation to send and receive + * messages are the same for both. + */ + +IPCUnixSocket::IPCUnixSocket() + : fd_(-1), master_(false), notifier_(nullptr) +{ +} + +/** + * \brief Create an new IPC channel + * + * Create a new IPC channel. Returned on success is a file descriptor which + * needs to be passed to the slave process and used when attaching to the IPC + * channel using attach(). + * + * \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; + } + + fd_ = sockets[0]; + master_ = true; + + ret = configure(); + if (ret) + return ret; + + return sockets[1]; +} + +/** + * \brief Attach to an existing IPC channel + * \param[in] fd File descriptor + * + * Attach to an existing IPC channel. The \a fd argument is the file descriptor + * returned from create() in the master process and passed to the slave process + * to be able to establish the IPC channel. + * + * \return 0 on success or a negative error code otherwise + */ +int IPCUnixSocket::attach(int fd) +{ + fd_ = fd; + + return configure(); +} + +/** + * \brief Close the IPC channel + * + * Close the IPC channel, no communication is possible after close() have been + * called. + */ +void IPCUnixSocket::close() +{ + delete notifier_; + + if (fd_ == -1) + return; + + ::close(fd_); + + fd_ = -1; +} + +/** + * \brief Send a message payload + * \param[in] payload Message payload to send + * + * Queues the message payload for transmission to the other end of the IPC + * channel. + * + * \return 0 on success or a negative error code otherwise + */ +int IPCUnixSocket::send(Payload &payload) +{ + Header hdr; + int ret; + + if (fd_ < 0) + return -ENOTCONN; + + hdr.data = payload.data.size(); + hdr.fds = payload.fds.size(); + + ret = ::send(fd_, &hdr, sizeof(hdr), 0); + if (ret < 0) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to send: " << strerror(-ret); + return ret; + } + + ret = sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds); + if (ret) + return ret; + + return 0; +} + +/** + * \var IPCUnixSocket::payloadReceived + * \brief A Signal emitted when a message payload is received + */ + +int IPCUnixSocket::configure() +{ + notifier_ = new EventNotifier(fd_, EventNotifier::Read); + notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier); + + return 0; +} + +int IPCUnixSocket::sendData(void *buffer, size_t length, const 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; + 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::recv() +{ + Payload payload; + Header hdr; + int ret; + + if (fd_ < 0) + return -ENOTCONN; + + 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); + + ret = recvData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds); + if (ret) + return ret; + + payloadReceived.emit(payload); + + 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) +{ + recv(); +} + +} /* 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', From patchwork Thu Jun 27 02:09:55 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 1526 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 10F3D6192A for ; Thu, 27 Jun 2019 04:10:11 +0200 (CEST) X-Halon-ID: a5e93e1a-9880-11e9-8ab4-005056917a89 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-01.atm.binero.net (Halon) with ESMTPA id a5e93e1a-9880-11e9-8ab4-005056917a89; Thu, 27 Jun 2019 04:09:52 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Thu, 27 Jun 2019 04:09:55 +0200 Message-Id: <20190627020955.6166-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190627020955.6166-1-niklas.soderlund@ragnatech.se> References: <20190627020955.6166-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 2/2] test: ipc: unix: Add test for IPCUnixSocket X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 27 Jun 2019 02:10:11 -0000 Test that the IPC supports sending data and file descriptors over the IPC medium. To be able execute the test two parts are needed, one to drive the test and act as the libcamera (master) and a one to act as the IPA (slave). The master drives the testing posting requests to the slave to process and sometime respond to. A few different tests are preformed. - Master sends an array to the slave which responds with a reversed copy of the array. The master verifies that a reversed array is returned. - Master sends a list of file descriptors and ask the salve to calculate and respond with the sum of the size of the files. The master verifies that the calculate size is correct. - Master send a pre-computed size and a list of file descriptors and ask the slave to verify that the pre-computed size matches the sum of the size of the file descriptors. Signed-off-by: Niklas Söderlund --- test/ipc/meson.build | 12 ++ test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 3 files changed, 352 insertions(+) create mode 100644 test/ipc/meson.build create mode 100644 test/ipc/unixsocket.cpp diff --git a/test/ipc/meson.build b/test/ipc/meson.build new file mode 100644 index 0000000000000000..ca8375f35df91731 --- /dev/null +++ b/test/ipc/meson.build @@ -0,0 +1,12 @@ +ipc_tests = [ + [ 'unixsocket', 'unixsocket.cpp' ], +] + +foreach t : ipc_tests + exe = executable(t[0], t[1], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + + test(t[0], exe, suite : 'ipc', is_parallel : false) +endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp new file mode 100644 index 0000000000000000..c50d7463cee48e75 --- /dev/null +++ b/test/ipc/unixsocket.cpp @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket.cpp - Unix socket IPC test + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipc_unixsocket.h" +#include "test.h" + +#define CMD_CLOSE 0 +#define CMD_REVERSE 1 +#define CMD_LEN_CALC 2 +#define CMD_LEN_CMP 3 + +using namespace std; +using namespace libcamera; + +int calcLength(int fd) +{ + lseek(fd, 0, 0); + int size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, 0); + + return size; +} + +class UnixSocketTestSlave +{ +public: + UnixSocketTestSlave() + : exitCode_(EXIT_FAILURE) + { + dispatcher_ = CameraManager::instance()->eventDispatcher(); + exit_.store(false, std::memory_order_release); + ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived); + } + + int run(int fd) + { + if (ipc_.attach(fd)) { + cerr << "Failed to connect to IPC" << endl; + return EXIT_FAILURE; + } + + while (!exit_.load(std::memory_order_acquire)) + dispatcher_->processEvents(); + + ipc_.close(); + + return exitCode_; + } + +private: + void payloadReceived(const IPCUnixSocket::Payload &payload) + { + IPCUnixSocket::Payload response; + int ret; + + const uint8_t cmd = payload.data.front(); + + cout << "Slave received command " << static_cast(cmd) << endl; + + switch (cmd) { + case CMD_CLOSE: + stop(0); + break; + case CMD_REVERSE: { + std::vector raw(payload.data.begin() + 1, payload.data.end()); + std::reverse(raw.begin(), raw.end()); + raw.insert(raw.begin(), cmd); + + response.data = raw; + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Reverse fail" << endl; + stop(ret); + } + break; + } + case CMD_LEN_CALC: { + int size = 0; + for (int fd : payload.fds) + size += calcLength(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 fail" << endl; + stop(ret); + } + break; + } + case CMD_LEN_CMP: { + int size = 0; + for (int fd : payload.fds) + size += calcLength(fd); + + int cmp; + memcpy(&cmp, payload.data.data() + 1, sizeof(cmp)); + + if (cmp != size) { + cerr << "Compare fail" << endl; + stop(-ERANGE); + } + break; + } + default: + cerr << "Unknown command " << cmd << endl; + stop(-EINVAL); + } + } + + void stop(int code) + { + exitCode_ = code; + exit_.store(true, std::memory_order_release); + } + + IPCUnixSocket ipc_; + EventDispatcher *dispatcher_; + int exitCode_; + std::atomic exit_; +}; + +static const std::vector testPaths = { + "/proc/self/exe", + "/proc/self/exe", +}; + +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("/proc/self/exe", "/proc/self/exe", + 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 reverse; + + reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 }; + if (ipc_.send(reverse)) + return TestFail; + + CameraManager::instance()->eventDispatcher()->processEvents(); + + return 0; + } + + int testCalc() + { + IPCUnixSocket::Payload payload; + + calcSize_ = 0; + for (const std::string path : testPaths) { + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) + return TestFail; + + calcSize_ += calcLength(fd); + payload.fds.push_back(fd); + } + + payload.data.push_back(CMD_LEN_CALC); + if (ipc_.send(payload)) + return TestFail; + + CameraManager::instance()->eventDispatcher()->processEvents(); + + return 0; + } + + int testCmp() + { + IPCUnixSocket::Payload payload; + + int size = 0; + for (const std::string path : testPaths) { + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) + return TestFail; + + size += calcLength(fd); + payload.fds.push_back(fd); + } + + payload.data.resize(1 + sizeof(size)); + payload.data[0] = CMD_LEN_CMP; + memcpy(payload.data.data() + 1, &size, sizeof(size)); + + if (ipc_.send(payload)) + return TestFail; + + return 0; + } + + int run() + { + int slavefd = ipc_.create(); + if (slavefd < 0) + return TestFail; + + if (slaveStart(slavefd)) + return TestFail; + + ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived); + + /* Test reversing a string, this test sending only data. */ + ranRev_ = false; + if (testReverse()) + return TestFail; + + /* Test offloading a calculation, this test sending only FDs. */ + ranCalc_ = false; + if (testCalc()) + return TestFail; + + /* Test fire and forget, this tests sending data and FDs. */ + if (testCmp()) + return TestFail; + + /* Close slave connection. */ + IPCUnixSocket::Payload close; + close.data.push_back(CMD_CLOSE); + if (ipc_.send(close)) + return TestFail; + + ipc_.close(); + if (slaveStop()) + return TestFail; + + /* Check that all tests have executed. */ + if (!ranRev_ || !ranCalc_) { + cerr << "Not all messages responded to" << endl; + return TestFail; + } + + return TestPass; + } + +private: + void payloadReceived(const IPCUnixSocket::Payload &payload) + { + const uint8_t cmd = payload.data.front(); + cout << "Master received command " << static_cast(cmd) << endl; + + switch (cmd) { + case CMD_REVERSE: { + std::vector raw(payload.data.begin() + 1, payload.data.end()); + if (raw == std::vector{ 5, 4, 3, 2, 1 }) + ranRev_ = true; + else + cerr << "Reverse response invalid" << endl; + + break; + } + case CMD_LEN_CALC: { + int output; + memcpy(&output, payload.data.data() + 1, sizeof(output)); + if (output == calcSize_) + ranCalc_ = true; + else + cerr << "Calc response invalid" << endl; + + break; + } + default: + cerr << "Unknown command " << cmd << endl; + } + } + + pid_t pid_; + IPCUnixSocket ipc_; + + bool ranRev_; + bool ranCalc_; + int calcSize_; +}; + +/* + * 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) { + int ipcfd = std::stoi(argv[1]); + UnixSocketTestSlave slave; + return slave.run(ipcfd); + } + + return UnixSocketTest().execute(); +} diff --git a/test/meson.build b/test/meson.build index c36ac24796367501..3666f6b2385bd4ca 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ subdir('libtest') subdir('camera') subdir('ipa') +subdir('ipc') subdir('media_device') subdir('pipeline') subdir('stream')