From patchwork Fri Jun 21 04:15:18 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: 1491 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 F139161583 for ; Fri, 21 Jun 2019 06:16:22 +0200 (CEST) X-Halon-ID: 2c90f6eb-93db-11e9-8601-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 2c90f6eb-93db-11e9-8601-0050569116f7; Fri, 21 Jun 2019 06:15:17 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Fri, 21 Jun 2019 06:15:18 +0200 Message-Id: <20190621041519.29689-2-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190621041519.29689-1-niklas.soderlund@ragnatech.se> References: <20190621041519.29689-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 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: Fri, 21 Jun 2019 04:16:23 -0000 To be able to isolate an IPA component in a separate process and IPC mechanism is needed to communicate with it. Add a IPC mechanism based on Unix sockets which allows users to pass both data and file descriptors to 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, The implementation guarantees that a whole object is transmitted and received over IPC before it's handed of. This allows IPC users to not have to deal with buffering and may depend on that it only needs to deal with serialization/deserialization of complete object blobs. The implementation also provides a priv field in the IPC message header which is a 32 bit integer that can be used by IPA implementations that do not require a complex protocol header to describe what type of message is transmitted. Signed-off-by: Niklas Söderlund --- src/libcamera/include/ipc_unixsocket.h | 58 +++++ src/libcamera/ipc_unixsocket.cpp | 330 +++++++++++++++++++++++++ src/libcamera/meson.build | 2 + 3 files changed, 390 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..864fa93b1f190fb7 --- /dev/null +++ b/src/libcamera/include/ipc_unixsocket.h @@ -0,0 +1,58 @@ +/* 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 + +namespace libcamera { + +class IPCUnixSocket +{ +public: + struct Payload { + uint32_t priv; + std::vector data; + std::vector fds; + }; + + IPCUnixSocket(); + IPCUnixSocket(int fd); + + int create(); + int connect(); + void close(); + + int send(const Payload &payload); + int recv(Payload *payload, int timeout); + int call(const Payload &payload, Payload *response, int timeout); + +private: + struct Header { + uint32_t priv; + uint32_t data; + uint8_t fds; + }; + + int poll(int timeout); + + int sendData(const void *buffer, ssize_t length); + int recvData(void *buffer, ssize_t length, int timeout); + + int sendFds(const int32_t *fds, unsigned int num); + int recvFds(int32_t *fds, unsigned int num, int timeout); + + int fd_; + bool master_; +}; + +} /* 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..b34fa0317a18b37c --- /dev/null +++ b/src/libcamera/ipc_unixsocket.cpp @@ -0,0 +1,330 @@ +/* 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 +#include +#include + +#include "log.h" + +/** + * \file ipc_unixsocket.h + * \brief IPC mechanism based on Unix sockets + */ + +/* + * Markers to use in IPC protocol, there is no specific meaning to the values, + * but they should be unique. + */ +#define CMD_PING 0x1f +#define CMD_PONG 0xf1 +#define CMD_FD 0x77 + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPCUnixSocket) + +IPCUnixSocket::IPCUnixSocket() + : fd_(-1), master_(false) +{ +} + +IPCUnixSocket::IPCUnixSocket(int fd) + : fd_(fd), master_(false) +{ +} + +int IPCUnixSocket::create() +{ + int sockets[2]; + int ret; + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + if (ret) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to create socket pair: " << strerror(-ret); + return ret; + } + + fd_ = sockets[0]; + master_ = true; + + return sockets[1]; +} + +int IPCUnixSocket::connect() +{ + Payload payload = {}; + Payload response = {}; + + if (master_) { + payload.data.push_back(CMD_PING); + + if (call(payload, &response, 500)) + return -1; + + if (response.data[0] != CMD_PONG) + return -1; + } else { + if (recv(&payload, 500)) + return -1; + + if (payload.data[0] != CMD_PING) + return -1; + + response.data.push_back(CMD_PONG); + + if (send(response)) + return -1; + } + + return 0; +} + +void IPCUnixSocket::close() +{ + if (fd_ == -1) + return; + + ::close(fd_); + + fd_ = -1; +} + +int IPCUnixSocket::send(const Payload &payload) +{ + Header hdr; + int ret; + + if (fd_ < 0) + return -ENOTCONN; + + hdr.priv = payload.priv; + hdr.data = payload.data.size(); + hdr.fds = payload.fds.size(); + + ret = sendData(&hdr, sizeof(hdr)); + if (ret) + return ret; + + if (hdr.data) { + ret = sendData(payload.data.data(), hdr.data); + if (ret) + return ret; + } + + if (hdr.fds) { + ret = sendFds(payload.fds.data(), hdr.fds); + if (ret) + return ret; + } + + return 0; +} + +int IPCUnixSocket::recv(Payload *payload, int timeout) +{ + Header hdr; + int ret; + + if (fd_ < 0) + return -ENOTCONN; + + ret = recvData(&hdr, sizeof(hdr), timeout); + if (ret) + return ret; + + payload->priv = hdr.priv; + payload->data.resize(hdr.data); + payload->fds.resize(hdr.fds); + + if (hdr.data) { + ret = recvData(payload->data.data(), hdr.data, timeout); + if (ret) + return ret; + } + + if (hdr.fds) { + ret = recvFds(payload->fds.data(), hdr.fds, timeout); + if (ret) + return ret; + } + + return 0; +} + +int IPCUnixSocket::call(const Payload &payload, Payload *response, int timeout) +{ + int ret = send(payload); + if (ret) + return ret; + + return recv(response, timeout); +} + +int IPCUnixSocket::poll(int timeout) +{ + struct pollfd pollfd = { fd_, POLLIN, 0 }; + + int ret = ::poll(&pollfd, 1, timeout); + if (ret < 0) { + ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to poll: " << strerror(-ret); + return ret; + } else if (ret == 0) { + return -ETIMEDOUT; + } + + return 0; +} + +int IPCUnixSocket::sendData(const void *buffer, ssize_t length) +{ + ssize_t len, sent; + const uint8_t *pos; + + if (fd_ < 0) + return -ENOTCONN; + + pos = static_cast(buffer); + len = length; + + while (len) { + sent = ::send(fd_, pos, len, 0); + if (sent < 0) { + sent = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to send: " << strerror(-sent); + return sent; + } + + pos += sent; + len -= sent; + } + + return 0; +} + +int IPCUnixSocket::recvData(void *buffer, ssize_t length, int timeout) +{ + ssize_t len, recived; + uint8_t *pos; + + if (fd_ < 0) + return -ENOTCONN; + + pos = static_cast(buffer); + len = length; + + while (len) { + int ret = poll(timeout); + if (ret < 0) + return ret; + + recived = ::recv(fd_, pos, len, 0); + if (recived < 0) { + recived = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to recv: " << strerror(-recived); + return recived; + } + + pos += recived; + len -= recived; + } + + return 0; +} + +int IPCUnixSocket::sendFds(const int32_t *fds, unsigned int num) +{ + char cmd = CMD_FD; + struct iovec iov[1]; + iov[0].iov_base = &cmd; + iov[0].iov_len = sizeof(cmd); + + 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::recvFds(int32_t *fds, unsigned int num, int timeout) +{ + char cmd; + struct iovec iov[1]; + iov[0].iov_base = &cmd; + iov[0].iov_len = sizeof(cmd); + + 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; + + int ret = poll(timeout); + if (ret < 0) + return ret; + + if (recvmsg(fd_, &msg, 0) < 0) { + int ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to recvmsg: " << strerror(-ret); + return ret; + } + + if (cmd != CMD_FD) { + LOG(IPCUnixSocket, Error) << "FD marker wrong"; + return -EINVAL; + } + + memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t)); + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index f26ad5b2dc57c014..1158825fa5b0702d 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', @@ -37,6 +38,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 Fri Jun 21 04:15:19 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: 1492 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 7755361583 for ; Fri, 21 Jun 2019 06:16:23 +0200 (CEST) X-Halon-ID: 2d3ab96c-93db-11e9-8601-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [145.14.112.32]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 2d3ab96c-93db-11e9-8601-0050569116f7; Fri, 21 Jun 2019 06:15:17 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Fri, 21 Jun 2019 06:15:19 +0200 Message-Id: <20190621041519.29689-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190621041519.29689-1-niklas.soderlund@ragnatech.se> References: <20190621041519.29689-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 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: Fri, 21 Jun 2019 04:16:23 -0000 Test that the IPC supports sending data and file descriptors over the IPC medium. To be able execute the test two executables 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 a string to the slave which responds with the reversed string. The master verifies that a reversed string is indeed 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 | 20 ++++ test/ipc/unixsocket-slave.cpp | 92 ++++++++++++++++ test/ipc/unixsocket.cpp | 200 ++++++++++++++++++++++++++++++++++ test/ipc/unixsocket.h | 27 +++++ test/meson.build | 1 + 5 files changed, 340 insertions(+) create mode 100644 test/ipc/meson.build create mode 100644 test/ipc/unixsocket-slave.cpp create mode 100644 test/ipc/unixsocket.cpp create mode 100644 test/ipc/unixsocket.h diff --git a/test/ipc/meson.build b/test/ipc/meson.build new file mode 100644 index 0000000000000000..0a425d4e7241c753 --- /dev/null +++ b/test/ipc/meson.build @@ -0,0 +1,20 @@ +# Tests are listed in order of complexity. +# They are not alphabetically sorted. +ipc_tests = [ + [ 'unixsocket', 'unixsocket.cpp', 'unixsocket-slave', 'unixsocket-slave.cpp' ], +] + +foreach t : ipc_tests + exe = executable(t[0], t[1], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + + slave = executable(t[2], t[3], + dependencies : libcamera_dep, + include_directories : test_includes_internal) + + test(t[0], exe, suite : 'ipc', is_parallel : false) +endforeach + +config_h.set('IPC_TEST_DIR', '"' + meson.current_build_dir() + '"') diff --git a/test/ipc/unixsocket-slave.cpp b/test/ipc/unixsocket-slave.cpp new file mode 100644 index 0000000000000000..ec27f6bf29823173 --- /dev/null +++ b/test/ipc/unixsocket-slave.cpp @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket-slave.cpp - Unix socket IPC slave runner + */ + +#include "unixsocket.h" + +#include +#include +#include +#include + +#include "ipc_unixsocket.h" + +using namespace std; +using namespace libcamera; + +int main(int argc, char **argv) +{ + if (argc != 2) { + cerr << "usage: %s " << endl; + return EXIT_FAILURE; + } + + int ipcfd = std::stoi(argv[1]); + IPCUnixSocket ipc(ipcfd); + + if (ipc.connect()) { + cerr << "Failed to connect to IPC" << endl; + return EXIT_FAILURE; + } + + bool run = true; + while (run) { + int ret = 0; + IPCUnixSocket::Payload payload, response; + + ret = ipc.recv(&payload, 100); + if (ret < 0) { + if (ret == -ETIMEDOUT) + continue; + return ret; + } + switch (payload.priv) { + case CMD_CLOSE: + run = false; + break; + case CMD_REVERESE: { + std::string str(payload.data.begin(), payload.data.end()); + std::reverse(str.begin(), str.end()); + response.data = std::vector(str.begin(), str.end()); + ret = ipc.send(response); + if (ret < 0) + return ret; + break; + } + case CMD_LEN_CALC: { + int size = 0; + for (int fd : payload.fds) + size += calcLength(fd); + + response.data.resize(sizeof(size)); + memcpy(response.data.data(), &size, sizeof(size)); + ret = ipc.send(response); + if (ret < 0) + return ret; + break; + } + case CMD_LEN_CMP: { + int size = 0; + for (int fd : payload.fds) + size += calcLength(fd); + + int cmp; + memcpy(&cmp, payload.data.data(), sizeof(cmp)); + + if (cmp != size) + return -ERANGE; + break; + } + default: + cerr << "Unkown command " << payload.priv << endl; + return -EINVAL; + } + } + + ipc.close(); + + return 0; +} diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp new file mode 100644 index 0000000000000000..ad2609764166a852 --- /dev/null +++ b/test/ipc/unixsocket.cpp @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket.cpp - Unix socket IPC test + */ + +#include "unixsocket.h" + +#include +#include +#include +#include +#include +#include + +#include "ipc_unixsocket.h" +#include "test.h" + +#define MASTER_BIN IPC_TEST_DIR "/unixsocket" +#define SLAVE_BIN IPC_TEST_DIR "/unixsocket-slave" + +using namespace std; +using namespace libcamera; + +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(SLAVE_BIN, SLAVE_BIN, arg.c_str()); + + /* 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() + { + std::string input = "FooBar"; + std::string match = "raBooF"; + + IPCUnixSocket::Payload payload, response; + + payload.priv = CMD_REVERESE; + payload.data = std::vector(input.begin(), input.end()); + + if (ipc_.call(payload, &response, 100)) + return TestFail; + + std::string output(response.data.begin(), response.data.end()); + + if (output != match) + return TestFail; + + return 0; + } + + int testCalc() + { + int fdM = open(MASTER_BIN, O_RDONLY); + int fdS = open(SLAVE_BIN, O_RDONLY); + + if (fdM < 0 || fdS < 0) + return TestFail; + + int size = 0; + size += calcLength(fdM); + size += calcLength(fdS); + + IPCUnixSocket::Payload payload, response; + + payload.priv = CMD_LEN_CALC; + payload.fds.push_back(fdM); + payload.fds.push_back(fdS); + + if (ipc_.call(payload, &response, 100)) + return TestFail; + + int output; + memcpy(&output, response.data.data(), sizeof(output)); + + if (output != size) + return TestFail; + + return 0; + } + + int testCmp() + { + int fdM = open(MASTER_BIN, O_RDONLY); + int fdS = open(SLAVE_BIN, O_RDONLY); + + if (fdM < 0 || fdS < 0) + return TestFail; + + int size = 0; + size += calcLength(fdM); + size += calcLength(fdS); + + IPCUnixSocket::Payload payload, response; + + payload.priv = CMD_LEN_CMP; + payload.data.resize(sizeof(size)); + memcpy(payload.data.data(), &size, sizeof(size)); + payload.fds.push_back(fdM); + payload.fds.push_back(fdS); + + if (ipc_.send(payload)) + return TestFail; + + return 0; + } + + int testClose() + { + IPCUnixSocket::Payload payload; + + payload.priv = CMD_CLOSE; + + if (ipc_.send(payload)) + return TestFail; + + return 0; + } + + int run() + { + int slavefd; + + slavefd = ipc_.create(); + if (slavefd < 0) + return TestFail; + + if (slaveStart(slavefd)) + return TestFail; + + if (ipc_.connect()) { + cerr << "Failed to connect to IPC" << endl; + return TestFail; + } + if (testReverse()) { + cerr << "String reverse fail" << endl; + return TestFail; + } + + if (testCalc()) { + cerr << "Size calc fail" << endl; + return TestFail; + } + + if (testCmp()) { + cerr << "Compare fail" << endl; + return TestFail; + } + + if (testClose()) + return TestFail; + + printf("Master OK!\n"); + + ipc_.close(); + + if (slaveStop()) + return TestFail; + + return TestPass; + } + +private: + pid_t pid_; + IPCUnixSocket ipc_; +}; + +TEST_REGISTER(UnixSocketTest) diff --git a/test/ipc/unixsocket.h b/test/ipc/unixsocket.h new file mode 100644 index 0000000000000000..5ae223c76108a4f6 --- /dev/null +++ b/test/ipc/unixsocket.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket.h - Unix socket IPC test + * + */ +#ifndef __LIBCAMERA_IPCUNIXSOCKET_TEST_H__ +#define __LIBCAMERA_IPCUNIXSOCKET_TEST_H__ + +#include + +#define CMD_CLOSE 0 +#define CMD_REVERESE 1 +#define CMD_LEN_CALC 2 +#define CMD_LEN_CMP 3 + +int calcLength(int fd) +{ + lseek(fd, 0, 0); + int size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, 0); + + return size; +} + +#endif /* __LIBCAMERA_IPCUNIXSOCKET_TEST_H__ */ 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')