From patchwork Tue May 21 20:54:28 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 1250 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BBCCE60C40 for ; Tue, 21 May 2019 22:54:41 +0200 (CEST) Received: from localhost.localdomain (unknown [96.44.9.117]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 048E252C; Tue, 21 May 2019 22:54:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1558472081; bh=kDM/39NcplX3V9DkJ56k6l4J4goCaH+/6LM5MhWtrn0=; h=From:To:Cc:Subject:Date:From; b=TjHo2bIivH/f7on9ltFyKd6MWAwI1NOIYULhqi5kvFkwfEo11RknwO/CQmBwkU/Tu EYovSeWuTOY3Wb0K+pugRs+Rz3S1uHi8zqutmu4v8aDMa5SfnY91QjhppsaOzffh46 Qy9N5xaiTFZql8SqZ3zYbRHp1tkiVlMNkotjAm3k= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Tue, 21 May 2019 16:54:28 -0400 Message-Id: <20190521205429.6529-1-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 1/2] libcamera: ipa_module: add IPA shared library module 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: Tue, 21 May 2019 20:54:42 -0000 Implement a class to wrap around an IPA module shared object. For now, just load a struct IPAModuleInfo with symbol name ipaModuleInfo from an IPA module .so shared object. Also provide a public header file including the struct IPAModuleInfo, structured such that both C and C++ IPA modules are supported. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v5: - overloaded elfPointer use the other elfPointer in implementation Changes in v4: - added overloaded elfPointer to specify the size to check for in the case that it cannot be obtained from the type (eg. char[size]) - documentation formalization - other cosmetic changes Changes in v3: - created public header file for IPAModuleInfo (and for handling C vs C++ IPA modules) - made ELF loading/parsing functions static - got rid of bitClass_ - other cosmetic changes Changes in v2: - renamed LibLoader class to IPAModule - added documentation - added logging - check that bitness of the shared object is the same as libcamera - moved symbol loading ("init") to the constructor, and added isValid() - added elfPointer() to prevent segfaults when reading data from mmap - moved struct IPAModuleInfo out of IPAModule - rename getIPAModuleInfo() to IPAModuleInfo(), and make it return a const reference - added munmap after the mmap include/libcamera/ipa/ipa_module_info.h | 31 +++ include/libcamera/meson.build | 1 + src/libcamera/include/ipa_module.h | 35 +++ src/libcamera/ipa_module.cpp | 285 ++++++++++++++++++++++++ src/libcamera/meson.build | 2 + 5 files changed, 354 insertions(+) create mode 100644 include/libcamera/ipa/ipa_module_info.h create mode 100644 src/libcamera/include/ipa_module.h create mode 100644 src/libcamera/ipa_module.cpp diff --git a/include/libcamera/ipa/ipa_module_info.h b/include/libcamera/ipa/ipa_module_info.h new file mode 100644 index 0000000..4e0d668 --- /dev/null +++ b/include/libcamera/ipa/ipa_module_info.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * ipa_module_info.h - Image Processing Algorithm module information + */ +#ifndef __LIBCAMERA_IPA_MODULE_INFO_H__ +#define __LIBCAMERA_IPA_MODULE_INFO_H__ + +#ifdef __cplusplus +namespace libcamera { +#endif + +struct IPAModuleInfo { + char name[256]; + unsigned int version; +}; + +#ifdef __cplusplus +extern "C" { +#endif +extern const struct IPAModuleInfo ipaModuleInfo; +#ifdef __cplusplus +}; +#endif + +#ifdef __cplusplus +}; /* namespace libcamera */ +#endif + +#endif /* __LIBCAMERA_IPA_MODULE_INFO_H__ */ diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index 83d226a..cb64f0c 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -5,6 +5,7 @@ libcamera_api = files([ 'event_dispatcher.h', 'event_notifier.h', 'geometry.h', + 'ipa/ipa_module_info.h', 'libcamera.h', 'object.h', 'request.h', diff --git a/src/libcamera/include/ipa_module.h b/src/libcamera/include/ipa_module.h new file mode 100644 index 0000000..671b5a9 --- /dev/null +++ b/src/libcamera/include/ipa_module.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * ipa_module.h - Image Processing Algorithm module + */ +#ifndef __LIBCAMERA_IPA_MODULE_H__ +#define __LIBCAMERA_IPA_MODULE_H__ + +#include +#include + +namespace libcamera { + +class IPAModule +{ +public: + explicit IPAModule(const std::string &libPath); + + bool isValid() const; + + const struct IPAModuleInfo &info() const; + +private: + struct IPAModuleInfo info_; + + std::string libPath_; + bool valid_; + + int loadIPAModuleInfo(); +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_IPA_MODULE_H__ */ diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp new file mode 100644 index 0000000..aa0fee6 --- /dev/null +++ b/src/libcamera/ipa_module.cpp @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * ipa_module.cpp - Image Processing Algorithm module + */ + +#include "ipa_module.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" + +/** + * \file ipa_module.h + * \brief Image Processing Algorithm module + */ + +/** + * \file ipa_module_info.h + * \brief Image Processing Algorithm module information + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPAModule) + +namespace { + +template +typename std::remove_extent::type *elfPointer(void *map, off_t offset, + size_t fileSize, size_t objSize) +{ + size_t size = offset + objSize; + if (size > fileSize || size < objSize) + return nullptr; + + return reinterpret_cast::type *> + (static_cast(map) + offset); +} + +template +typename std::remove_extent::type *elfPointer(void *map, off_t offset, + size_t fileSize) +{ + return elfPointer(map, offset, fileSize, sizeof(T)); +} + +int elfVerifyIdent(void *map, size_t soSize) +{ + char *e_ident = elfPointer(map, 0, soSize); + if (!e_ident) + return -ENOEXEC; + + if (e_ident[EI_MAG0] != ELFMAG0 || + e_ident[EI_MAG1] != ELFMAG1 || + e_ident[EI_MAG2] != ELFMAG2 || + e_ident[EI_MAG3] != ELFMAG3 || + e_ident[EI_VERSION] != EV_CURRENT) + return -ENOEXEC; + + int bitClass = sizeof(unsigned long) == 4 ? ELFCLASS32 : ELFCLASS64; + if (e_ident[EI_CLASS] != bitClass) + return -ENOEXEC; + + int a = 1; + unsigned char endianness = *reinterpret_cast(&a) == 1 + ? ELFDATA2LSB : ELFDATA2MSB; + if (e_ident[EI_DATA] != endianness) + return -ENOEXEC; + + return 0; +} + +template +int elfLoadSymbol(void *dst, size_t size, void *map, size_t soSize, + const char *symbol) +{ + ElfHeader *eHdr = elfPointer(map, 0, soSize); + if (!eHdr) + return -ENOEXEC; + + off_t offset = eHdr->e_shoff + eHdr->e_shentsize * eHdr->e_shstrndx; + SecHeader *sHdr = elfPointer(map, offset, soSize); + if (!sHdr) + return -ENOEXEC; + off_t shnameoff = sHdr->sh_offset; + + /* Locate .dynsym section header. */ + SecHeader *dynsym = nullptr; + for (unsigned int i = 0; i < eHdr->e_shnum; i++) { + offset = eHdr->e_shoff + eHdr->e_shentsize * i; + sHdr = elfPointer(map, offset, soSize); + if (!sHdr) + return -ENOEXEC; + + offset = shnameoff + sHdr->sh_name; + char *name = elfPointer(map, offset, soSize); + if (!name) + return -ENOEXEC; + + if (sHdr->sh_type == SHT_DYNSYM && !strcmp(name, ".dynsym")) { + dynsym = sHdr; + break; + } + } + + if (dynsym == nullptr) { + LOG(IPAModule, Error) << "ELF has no .dynsym section"; + return -ENOEXEC; + } + + offset = eHdr->e_shoff + eHdr->e_shentsize * dynsym->sh_link; + sHdr = elfPointer(map, offset, soSize); + if (!sHdr) + return -ENOEXEC; + off_t dynsym_nameoff = sHdr->sh_offset; + + /* Locate symbol in the .dynsym section. */ + SymHeader *targetSymbol = nullptr; + unsigned int dynsym_num = dynsym->sh_size / dynsym->sh_entsize; + for (unsigned int i = 0; i < dynsym_num; i++) { + offset = dynsym->sh_offset + dynsym->sh_entsize * i; + SymHeader *sym = elfPointer(map, offset, soSize); + if (!sym) + return -ENOEXEC; + + offset = dynsym_nameoff + sym->st_name; + char *name = elfPointer(map, offset, soSize, + strlen(symbol) + 1); + if (!name) + return -ENOEXEC; + + if (!strcmp(name, symbol) && + sym->st_info & STB_GLOBAL && sym->st_size == size) { + targetSymbol = sym; + break; + } + } + + if (targetSymbol == nullptr) { + LOG(IPAModule, Error) << "Symbol " << symbol << " not found"; + return -ENOEXEC; + } + + /* Locate and return data of symbol. */ + if (targetSymbol->st_shndx >= eHdr->e_shnum) + return -ENOEXEC; + offset = eHdr->e_shoff + targetSymbol->st_shndx * eHdr->e_shentsize; + sHdr = elfPointer(map, offset, soSize); + if (!sHdr) + return -ENOEXEC; + offset = sHdr->sh_offset + (targetSymbol->st_value - sHdr->sh_addr); + char *data = elfPointer(map, offset, soSize, size); + if (!data) + return -ENOEXEC; + + memcpy(dst, data, size); + + return 0; +} + +} /* namespace */ + +/** + * \struct IPAModuleInfo + * \brief Information of an IPA module + * + * This structure contains the information of an IPA module. It is loaded, + * read, and validated before anything else is loaded from the shared object. + * + * \var IPAModuleInfo::name + * \brief The name of the IPA module + * + * \var IPAModuleInfo::version + * \brief The version of the IPA module + * + * \todo abi compatability version + * \todo pipeline compatability matcher + */ + +/** + * \class IPAModule + * \brief Wrapper around IPA module shared object + */ + +/** + * \brief Construct an IPAModule instance + * \param[in] libPath path to IPA module shared object + * + * Loads the IPAModuleInfo from the IPA module shared object at libPath. + * The IPA module shared object file must be of the same endianness and + * bitness as libcamera. + * + * \todo load funtions from the IPA to be used by pipelines + * + * The caller shall call the isValid() method after constructing an + * IPAModule instance to verify the validity of the IPAModule. + */ +IPAModule::IPAModule(const std::string &libPath) + : libPath_(libPath), valid_(false) +{ + if (loadIPAModuleInfo() < 0) + return; + + valid_ = true; +} + +int IPAModule::loadIPAModuleInfo() +{ + int fd = open(libPath_.c_str(), O_RDONLY); + if (fd < 0) { + int ret = -errno; + LOG(IPAModule, Error) << "Failed to open IPA library: " + << strerror(-ret); + return ret; + } + + size_t soSize; + void *map; + struct stat st; + int ret = fstat(fd, &st); + if (ret < 0) + goto close; + soSize = st.st_size; + map = mmap(NULL, soSize, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + ret = -errno; + goto close; + } + + ret = elfVerifyIdent(map, soSize); + if (ret) + goto unmap; + + if (sizeof(unsigned long) == 4) + ret = elfLoadSymbol + (&info_, sizeof(info_), map, soSize, "ipaModuleInfo"); + else + ret = elfLoadSymbol + (&info_, sizeof(info_), map, soSize, "ipaModuleInfo"); + +unmap: + munmap(map, soSize); +close: + close(fd); + return ret; +} + +/** + * \brief Check if the IPAModule instance is valid + * + * An IPAModule instance is valid if the IPA module shared object exists and + * the IPA module information it contains was successfully retrieved and + * validated. + * + * \return true if the the IPAModule is valid, false otherwise + */ +bool IPAModule::isValid() const +{ + return valid_; +} + +/** + * \brief Retrieve the IPA module information + * + * The content of the IPA module information is loaded from the module, + * and is valid only if the module is valid (as returned by isValid()). + * Calling this function on an invalid module is an error. + * + * \return the IPA module information + */ +const struct IPAModuleInfo &IPAModule::info() const +{ + return info_; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 8796f49..e5b48f2 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -10,6 +10,7 @@ libcamera_sources = files([ 'event_notifier.cpp', 'formats.cpp', 'geometry.cpp', + 'ipa_module.cpp', 'log.cpp', 'media_device.cpp', 'media_object.cpp', @@ -31,6 +32,7 @@ libcamera_headers = files([ 'include/device_enumerator_udev.h', 'include/event_dispatcher_poll.h', 'include/formats.h', + 'include/ipa_module.h', 'include/log.h', 'include/media_device.h', 'include/media_object.h', From patchwork Tue May 21 20:54:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 1251 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4630E60DE9 for ; Tue, 21 May 2019 22:54:42 +0200 (CEST) Received: from localhost.localdomain (unknown [96.44.9.117]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A2EC854B; Tue, 21 May 2019 22:54:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1558472082; bh=7Yff0Vn+TPyWrKoXAfPSXUK0kPQVCYRvQe0X5e418A8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hYykQ0A4Ga1ACWDAJRUSUHzUoUL/W5IDw8y7+m6+cUwT+w8j0mG5OV/GWVWI65zVP rkfNy0E0S9pQethEbyF4kErCPBlmm5VFYQ2zZ7AlgT/eyH32ClBQN5+BPPSeoHwiBZ krRhYFSCRn1/NPw2b5DaiGfgRi6kAYij98CJRRCo= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Tue, 21 May 2019 16:54:29 -0400 Message-Id: <20190521205429.6529-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190521205429.6529-1-paul.elder@ideasonboard.com> References: <20190521205429.6529-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 2/2] tests: ipa: add tests to test IPAModule 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: Tue, 21 May 2019 20:54:42 -0000 Add tests to test the the IPAModule class, for loading the IPA module info from IPA module .so shared objects, with modules written in both C and C++. Signed-off-by: Paul Elder --- Changes in v5: - remove non-error test output - auto-check if the correct module info was loaded - make the meson build for the test shared objects prettier No changes in v4 Changes in v3: - remove tests for incorrect bitness - make the test IPA module .so a C one and a C++ one Changes in v2: - added source for test .so - updated tests to work with new (v2, see 1/2) IPAModule API test/ipa/ipa_test.cpp | 79 ++++++++++++++++++++++++++++++++++++++++ test/ipa/meson.build | 21 +++++++++++ test/ipa/shared_test.c | 6 +++ test/ipa/shared_test.cpp | 12 ++++++ test/meson.build | 1 + 5 files changed, 119 insertions(+) create mode 100644 test/ipa/ipa_test.cpp create mode 100644 test/ipa/meson.build create mode 100644 test/ipa/shared_test.c create mode 100644 test/ipa/shared_test.cpp diff --git a/test/ipa/ipa_test.cpp b/test/ipa/ipa_test.cpp new file mode 100644 index 0000000..fb8eeba --- /dev/null +++ b/test/ipa/ipa_test.cpp @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * load-so.cpp - loading .so tests + */ + +#include +#include + +#include "ipa_module.h" + +#include "test.h" + +using namespace std; +using namespace libcamera; + +class IPAModuleTest : public Test +{ +protected: + int init() + { + strcpy(testInfo_.name, "It's over nine thousand!"); + testInfo_.version = 9001; + + return 0; + } + + int runTest(const string &path) + { + int ret = 0; + struct IPAModuleInfo info; + + IPAModule *ll = new IPAModule(path); + + if (!ll->isValid()) { + cerr << "test IPA module is invalid" << endl; + ret = -1; + goto done; + } + + info = ll->info(); + // todo validate instead of print + if (strcmp(info.name, testInfo_.name)) { + cerr << "test IPA module has incorrect name" << endl; + cerr << "expected \"" << testInfo_.name << "\", got \"" << info.name << "\"" << endl; + ret = -1; + } + + if (info.version != testInfo_.version) { + cerr << "test IPA module has incorrect version" << endl; + cerr << "expected \"" << testInfo_.version << "\", got \"" << info.version << "\"" << endl; + ret = -1; + } + + done: + delete ll; + return ret; + } + + int run() override + { + int count = 0; + + count += runTest("test/ipa/ipa-dummy.so"); + + count += runTest("test/ipa/ipa-dummy-cpp.so"); + + if (count < 0) + return TestFail; + + return TestPass; + } + +private: + struct IPAModuleInfo testInfo_; +}; + +TEST_REGISTER(IPAModuleTest) diff --git a/test/ipa/meson.build b/test/ipa/meson.build new file mode 100644 index 0000000..57c6a1c --- /dev/null +++ b/test/ipa/meson.build @@ -0,0 +1,21 @@ +ipa_modules_sources = [ + ['ipa-dummy', 'shared_test.c'], + ['ipa-dummy-cpp', 'shared_test.cpp'], +] + +foreach m : ipa_modules_sources + shared_library(m, name_prefix: '', + include_directories: test_includes_public) +endforeach + +ipa_test = [ + ['ipa_test', 'ipa_test.cpp'], +] + +foreach t : ipa_test + exe = executable(t[0], t[1], + link_with : test_libraries, + include_directories : test_includes_internal) + + test(t[0], exe, suite: 'ipa', is_parallel: false) +endforeach diff --git a/test/ipa/shared_test.c b/test/ipa/shared_test.c new file mode 100644 index 0000000..87d182b --- /dev/null +++ b/test/ipa/shared_test.c @@ -0,0 +1,6 @@ +#include + +const struct IPAModuleInfo ipaModuleInfo = { + .name = "It's over nine thousand!", + .version = 9001, +}; diff --git a/test/ipa/shared_test.cpp b/test/ipa/shared_test.cpp new file mode 100644 index 0000000..4e5c976 --- /dev/null +++ b/test/ipa/shared_test.cpp @@ -0,0 +1,12 @@ +#include + +namespace libcamera { + +extern "C" { +const struct libcamera::IPAModuleInfo ipaModuleInfo = { + "It's over nine thousand!", + 9001, +}; +}; + +}; /* namespace libcamera */ diff --git a/test/meson.build b/test/meson.build index d501f2b..ef41367 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,6 +1,7 @@ subdir('libtest') subdir('camera') +subdir('ipa') subdir('media_device') subdir('pipeline') subdir('v4l2_device')