From patchwork Thu Jul 3 11:42:16 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23731 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 01F53BDCBF for ; Thu, 3 Jul 2025 11:42:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A978F68E42; Thu, 3 Jul 2025 13:42:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="u4qe6wE1"; dkim-atps=neutral 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 531D461528 for ; Thu, 3 Jul 2025 13:42:46 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D1D3C667; Thu, 3 Jul 2025 13:42:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542942; bh=bQ696tnewOoI16peZTkVNGpditS9eR+yeFQVZR2JD5I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=u4qe6wE10DL1tLjwkPHuuodl9o8f1SCVDpvJ4rTLpdlYATElAGIiOZOHuh+lxXauX 5ki8JtP1lexseCNRNwcTvBvh0jbCU9/OJCj3qt49SiQBIQcKq6EY8wpOFGryQCoFmc pMvLxk/oO3YMx14MD8rib0fGk/IcAaiYB3+SDOks= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 1/8] libcamera: ipa_manager: Factor out .so file searching Date: Thu, 3 Jul 2025 20:42:16 +0900 Message-ID: <20250703114225.2074071-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" In the near future we will add support for layers/shoes on top of libcamera, which will need to search for shared object files similar to how IPAManager does. To avoid code duplication, move this code out of IPAManager into internal utils. Signed-off-by: Paul Elder --- No change in v2 --- include/libcamera/internal/ipa_manager.h | 4 - include/libcamera/internal/meson.build | 1 + include/libcamera/internal/utils.h | 26 ++++++ src/libcamera/ipa_manager.cpp | 108 ++++------------------- src/libcamera/meson.build | 1 + src/libcamera/utils.cpp | 107 ++++++++++++++++++++++ 6 files changed, 154 insertions(+), 93 deletions(-) create mode 100644 include/libcamera/internal/utils.h create mode 100644 src/libcamera/utils.cpp diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index a0d448cf9862..6b0ba4b5477f 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -59,10 +59,6 @@ public: #endif private: - void parseDir(const char *libDir, unsigned int maxDepth, - std::vector &files); - unsigned int addDir(const char *libDir, unsigned int maxDepth = 0); - IPAModule *module(PipelineHandler *pipe, uint32_t minVersion, uint32_t maxVersion); diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 5c80a28c4cbe..690f5c5ec9f6 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -41,6 +41,7 @@ libcamera_internal_headers = files([ 'shared_mem_object.h', 'source_paths.h', 'sysfs.h', + 'utils.h', 'v4l2_device.h', 'v4l2_pixelformat.h', 'v4l2_subdevice.h', diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h new file mode 100644 index 000000000000..742657bebb28 --- /dev/null +++ b/include/libcamera/internal/utils.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * Miscellaneous utility functions + */ + +#pragma once + +#include +#include +#include + +namespace libcamera { + +namespace utils { + +void parseDir(const char *libDir, unsigned int maxDepth, + std::vector &files); + +unsigned int addDir(const char *libDir, unsigned int maxDepth, + std::function func); + +} /* namespace utils */ + +} /* namespace libcamera */ diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index 830750dcc0fb..ecad4845a077 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -8,9 +8,8 @@ #include "libcamera/internal/ipa_manager.h" #include -#include +#include #include -#include #include #include @@ -19,6 +18,7 @@ #include "libcamera/internal/ipa_module.h" #include "libcamera/internal/ipa_proxy.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/utils.h" /** * \file ipa_manager.h @@ -110,6 +110,20 @@ IPAManager::IPAManager() unsigned int ipaCount = 0; + auto &modules = modules_; + std::function addDirHandler = + [&modules](const std::string &file) { + auto ipaModule = std::make_unique(file); + if (!ipaModule->isValid()) + return 0; + + LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'"; + + modules.push_back(std::move(ipaModule)); + + return 1; + }; + /* User-specified paths take precedence. */ const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH"); if (modulePaths) { @@ -117,7 +131,7 @@ IPAManager::IPAManager() if (dir.empty()) continue; - ipaCount += addDir(dir.c_str()); + ipaCount += utils::addDir(dir.c_str(), 0, addDirHandler); } if (!ipaCount) @@ -138,11 +152,11 @@ IPAManager::IPAManager() << "libcamera is not installed. Adding '" << ipaBuildPath << "' to the IPA search path"; - ipaCount += addDir(ipaBuildPath.c_str(), maxDepth); + ipaCount += utils::addDir(ipaBuildPath.c_str(), maxDepth, addDirHandler); } /* Finally try to load IPAs from the installed system path. */ - ipaCount += addDir(IPA_MODULE_DIR); + ipaCount += utils::addDir(IPA_MODULE_DIR, 0, addDirHandler); if (!ipaCount) LOG(IPAManager, Warning) @@ -151,90 +165,6 @@ IPAManager::IPAManager() IPAManager::~IPAManager() = default; -/** - * \brief Identify shared library objects within a directory - * \param[in] libDir The directory to search for shared objects - * \param[in] maxDepth The maximum depth of sub-directories to parse - * \param[out] files A vector of paths to shared object library files - * - * Search a directory for .so files, allowing recursion down to sub-directories - * no further than the depth specified by \a maxDepth. - * - * Discovered shared objects are added to the \a files vector. - */ -void IPAManager::parseDir(const char *libDir, unsigned int maxDepth, - std::vector &files) -{ - struct dirent *ent; - DIR *dir; - - dir = opendir(libDir); - if (!dir) - return; - - while ((ent = readdir(dir)) != nullptr) { - if (ent->d_type == DT_DIR && maxDepth) { - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) - continue; - - std::string subdir = std::string(libDir) + "/" + ent->d_name; - - /* Recursion is limited to maxDepth. */ - parseDir(subdir.c_str(), maxDepth - 1, files); - - continue; - } - - int offset = strlen(ent->d_name) - 3; - if (offset < 0) - continue; - if (strcmp(&ent->d_name[offset], ".so")) - continue; - - files.push_back(std::string(libDir) + "/" + ent->d_name); - } - - closedir(dir); -} - -/** - * \brief Load IPA modules from a directory - * \param[in] libDir The directory to search for IPA modules - * \param[in] maxDepth The maximum depth of sub-directories to search - * - * This function tries to create an IPAModule instance for every shared object - * found in \a libDir, and skips invalid IPA modules. - * - * Sub-directories are searched up to a depth of \a maxDepth. A \a maxDepth - * value of 0 only searches the directory specified in \a libDir. - * - * \return Number of modules loaded by this call - */ -unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) -{ - std::vector files; - - parseDir(libDir, maxDepth, files); - - /* Ensure a stable ordering of modules. */ - std::sort(files.begin(), files.end()); - - unsigned int count = 0; - for (const std::string &file : files) { - auto ipaModule = std::make_unique(file); - if (!ipaModule->isValid()) - continue; - - LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'"; - - modules_.push_back(std::move(ipaModule)); - count++; - } - - return count; -} - /** * \brief Retrieve an IPA module that matches a given pipeline handler * \param[in] pipe The pipeline handler diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index de1eb99b28fd..6a71b2903d27 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -51,6 +51,7 @@ libcamera_internal_sources = files([ 'shared_mem_object.cpp', 'source_paths.cpp', 'sysfs.cpp', + 'utils.cpp', 'v4l2_device.cpp', 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp new file mode 100644 index 000000000000..ef046ac3134e --- /dev/null +++ b/src/libcamera/utils.cpp @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * Miscellaneous utility functions (internal) + */ + +#include "libcamera/internal/utils.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * \file internal/utils.h + * \brief Miscellaneous utility functions (internal) + */ + +namespace libcamera { + +namespace utils { + +/** + * \brief Identify shared library objects within a directory + * \param[in] libDir The directory to search for shared objects + * \param[in] maxDepth The maximum depth of sub-directories to parse + * \param[out] files A vector of paths to shared object library files + * + * Search a directory for .so files, allowing recursion down to sub-directories + * no further than the depth specified by \a maxDepth. + * + * Discovered shared objects are added to the \a files vector. + */ +void parseDir(const char *libDir, unsigned int maxDepth, + std::vector &files) +{ + struct dirent *ent; + DIR *dir; + + dir = opendir(libDir); + if (!dir) + return; + + while ((ent = readdir(dir)) != nullptr) { + if (ent->d_type == DT_DIR && maxDepth) { + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) + continue; + + std::string subdir = std::string(libDir) + "/" + ent->d_name; + + /* Recursion is limited to maxDepth. */ + parseDir(subdir.c_str(), maxDepth - 1, files); + + continue; + } + + int offset = strlen(ent->d_name) - 3; + if (offset < 0) + continue; + if (strcmp(&ent->d_name[offset], ".so")) + continue; + + files.push_back(std::string(libDir) + "/" + ent->d_name); + } + + closedir(dir); +} + +/** + * \brief Execute some function on shared object files from a directory + * \param[in] libDir The directory to search for shared objects + * \param[in] maxDepth The maximum depth of sub-directories to search + * \param[in] func The function to execute on every shared object + * + * This function tries to execute the given function \a func for every shared + * object found in \a libDir. + * + * Sub-directories are searched up to a depth of \a maxDepth. A \a maxDepth + * value of 0 only searches the directory specified in \a libDir. + * + * \return Number of shared objects loaded by this call + */ +unsigned int addDir(const char *libDir, unsigned int maxDepth, + std::function func) +{ + std::vector files; + + parseDir(libDir, maxDepth, files); + + /* Ensure a stable ordering of modules. */ + std::sort(files.begin(), files.end()); + + unsigned int count = 0; + for (const std::string &file : files) + count += func(file); + + return count; +} + +} /* namespace utils */ + +} /* namespace libcamera */ From patchwork Thu Jul 3 11:42:17 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23732 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 15EB0BDCBF for ; Thu, 3 Jul 2025 11:42:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4168C68E45; Thu, 3 Jul 2025 13:42:49 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ujE3lciZ"; dkim-atps=neutral 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 E0F6E61528 for ; Thu, 3 Jul 2025 13:42:47 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C684A593; Thu, 3 Jul 2025 13:42:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542944; bh=5nUCDXiKSMlsr2mU03aXlAg49nmpnihe+dtqAJisX8U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ujE3lciZX8Wdsrsp/UJ3NtqwmnLuUWFb3ek6y7GyNmeYqs8Esh9HTnrXdhl1bAgIo wZNc7OGXpKL7zIiEnnDDbyNEcIy7tuSjOilMm/Jf/H9NxBsZAmhIJCAHbCNL62COAR 0PlWlO/CjcLd94Zh1cIhp8X12e5+YFHcqxeCf8NI= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 2/8] libcamera: ipa_module: Factor out ELF file handling Date: Thu, 3 Jul 2025 20:42:17 +0900 Message-ID: <20250703114225.2074071-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" In the near future we will add support for layers/shoes on top of libcamera, which will need to validate and load symbols from ELF files similar to how IPAModule does. To avoid code duplication, move this code out of IPAModule into internal utils. Signed-off-by: Paul Elder --- No change in v2 --- include/libcamera/internal/utils.h | 6 ++ src/libcamera/ipa_module.cpp | 152 +--------------------------- src/libcamera/utils.cpp | 157 +++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 149 deletions(-) diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h index 742657bebb28..98d5094eb88d 100644 --- a/include/libcamera/internal/utils.h +++ b/include/libcamera/internal/utils.h @@ -8,9 +8,12 @@ #pragma once #include +#include #include #include +#include + namespace libcamera { namespace utils { @@ -21,6 +24,9 @@ void parseDir(const char *libDir, unsigned int maxDepth, unsigned int addDir(const char *libDir, unsigned int maxDepth, std::function func); +int elfVerifyIdent(Span elf); +Span elfLoadSymbol(Span elf, const char *symbol); + } /* namespace utils */ } /* namespace libcamera */ diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp index e6ea61e44829..c8559ec8b74c 100644 --- a/src/libcamera/ipa_module.cpp +++ b/src/libcamera/ipa_module.cpp @@ -25,6 +25,7 @@ #include #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/utils.h" /** * \file ipa_module.h @@ -40,153 +41,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPAModule) -namespace { - -template -typename std::remove_extent_t *elfPointer(Span elf, - off_t offset, size_t objSize) -{ - size_t size = offset + objSize; - if (size > elf.size() || size < objSize) - return nullptr; - - return reinterpret_cast *>( - reinterpret_cast(elf.data()) + offset); -} - -template -typename std::remove_extent_t *elfPointer(Span elf, - off_t offset) -{ - return elfPointer(elf, offset, sizeof(T)); -} - -int elfVerifyIdent(Span elf) -{ - const char *e_ident = elfPointer(elf, 0); - 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; -} - -const ElfW(Shdr) *elfSection(Span elf, const ElfW(Ehdr) *eHdr, - ElfW(Half) idx) -{ - if (idx >= eHdr->e_shnum) - return nullptr; - - off_t offset = eHdr->e_shoff + idx * - static_cast(eHdr->e_shentsize); - return elfPointer(elf, offset); -} - -/** - * \brief Retrieve address and size of a symbol from an mmap'ed ELF file - * \param[in] elf Address and size of mmap'ed ELF file - * \param[in] symbol Symbol name - * - * \return The memory region storing the symbol on success, or an empty span - * otherwise - */ -Span elfLoadSymbol(Span elf, const char *symbol) -{ - const ElfW(Ehdr) *eHdr = elfPointer(elf, 0); - if (!eHdr) - return {}; - - const ElfW(Shdr) *sHdr = elfSection(elf, eHdr, eHdr->e_shstrndx); - if (!sHdr) - return {}; - off_t shnameoff = sHdr->sh_offset; - - /* Locate .dynsym section header. */ - const ElfW(Shdr) *dynsym = nullptr; - for (unsigned int i = 0; i < eHdr->e_shnum; i++) { - sHdr = elfSection(elf, eHdr, i); - if (!sHdr) - return {}; - - off_t offset = shnameoff + sHdr->sh_name; - const char *name = elfPointer(elf, offset); - if (!name) - return {}; - - if (sHdr->sh_type == SHT_DYNSYM && !strcmp(name, ".dynsym")) { - dynsym = sHdr; - break; - } - } - - if (dynsym == nullptr) { - LOG(IPAModule, Error) << "ELF has no .dynsym section"; - return {}; - } - - sHdr = elfSection(elf, eHdr, dynsym->sh_link); - if (!sHdr) - return {}; - off_t dynsym_nameoff = sHdr->sh_offset; - - /* Locate symbol in the .dynsym section. */ - const ElfW(Sym) *targetSymbol = nullptr; - unsigned int dynsym_num = dynsym->sh_size / dynsym->sh_entsize; - for (unsigned int i = 0; i < dynsym_num; i++) { - off_t offset = dynsym->sh_offset + dynsym->sh_entsize * i; - const ElfW(Sym) *sym = elfPointer(elf, offset); - if (!sym) - return {}; - - offset = dynsym_nameoff + sym->st_name; - const char *name = elfPointer(elf, offset, - strlen(symbol) + 1); - if (!name) - return {}; - - if (!strcmp(name, symbol) && - sym->st_info & STB_GLOBAL) { - targetSymbol = sym; - break; - } - } - - if (targetSymbol == nullptr) { - LOG(IPAModule, Error) << "Symbol " << symbol << " not found"; - return {}; - } - - /* Locate and return data of symbol. */ - sHdr = elfSection(elf, eHdr, targetSymbol->st_shndx); - if (!sHdr) - return {}; - off_t offset = sHdr->sh_offset + (targetSymbol->st_value - sHdr->sh_addr); - const uint8_t *data = elfPointer(elf, offset, - targetSymbol->st_size); - if (!data) - return {}; - - return { data, targetSymbol->st_size }; -} - -} /* namespace */ - /** * \def IPA_MODULE_API_VERSION * \brief The IPA module API version @@ -280,13 +134,13 @@ int IPAModule::loadIPAModuleInfo() } Span data = file.map(); - int ret = elfVerifyIdent(data); + int ret = utils::elfVerifyIdent(data); if (ret) { LOG(IPAModule, Error) << "IPA module is not an ELF file"; return ret; } - Span info = elfLoadSymbol(data, "ipaModuleInfo"); + Span info = utils::elfLoadSymbol(data, "ipaModuleInfo"); if (info.size() < sizeof(info_)) { LOG(IPAModule, Error) << "IPA module has no valid info"; return -EINVAL; diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp index ef046ac3134e..f7bc5c54ff57 100644 --- a/src/libcamera/utils.cpp +++ b/src/libcamera/utils.cpp @@ -10,20 +10,63 @@ #include #include #include +#include +#include #include #include #include #include +#include + /** * \file internal/utils.h * \brief Miscellaneous utility functions (internal) */ +/* musl doesn't declare _DYNAMIC in link.h, declare it manually. */ +extern ElfW(Dyn) _DYNAMIC[]; + namespace libcamera { +namespace { + +template +typename std::remove_extent_t *elfPointer(Span elf, + off_t offset, size_t objSize) +{ + size_t size = offset + objSize; + if (size > elf.size() || size < objSize) + return nullptr; + + return reinterpret_cast *>( + reinterpret_cast(elf.data()) + offset); +} + +template +typename std::remove_extent_t *elfPointer(Span elf, + off_t offset) +{ + return elfPointer(elf, offset, sizeof(T)); +} + +const ElfW(Shdr) *elfSection(Span elf, const ElfW(Ehdr) *eHdr, + ElfW(Half) idx) +{ + if (idx >= eHdr->e_shnum) + return nullptr; + + off_t offset = eHdr->e_shoff + idx * + static_cast(eHdr->e_shentsize); + return elfPointer(elf, offset); +} + +} /* namespace */ + namespace utils { +LOG_DEFINE_CATEGORY(Utils) + /** * \brief Identify shared library objects within a directory * \param[in] libDir The directory to search for shared objects @@ -102,6 +145,120 @@ unsigned int addDir(const char *libDir, unsigned int maxDepth, return count; } +int elfVerifyIdent(Span elf) +{ + const char *e_ident = elfPointer(elf, 0); + 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; +} + +/** + * \brief Retrieve address and size of a symbol from an mmap'ed ELF file + * \param[in] elf Address and size of mmap'ed ELF file + * \param[in] symbol Symbol name + * + * \return The memory region storing the symbol on success, or an empty span + * otherwise + */ +Span elfLoadSymbol(Span elf, const char *symbol) +{ + const ElfW(Ehdr) *eHdr = elfPointer(elf, 0); + if (!eHdr) + return {}; + + const ElfW(Shdr) *sHdr = elfSection(elf, eHdr, eHdr->e_shstrndx); + if (!sHdr) + return {}; + off_t shnameoff = sHdr->sh_offset; + + /* Locate .dynsym section header. */ + const ElfW(Shdr) *dynsym = nullptr; + for (unsigned int i = 0; i < eHdr->e_shnum; i++) { + sHdr = elfSection(elf, eHdr, i); + if (!sHdr) + return {}; + + off_t offset = shnameoff + sHdr->sh_name; + const char *name = elfPointer(elf, offset); + if (!name) + return {}; + + if (sHdr->sh_type == SHT_DYNSYM && !strcmp(name, ".dynsym")) { + dynsym = sHdr; + break; + } + } + + if (dynsym == nullptr) { + LOG(Utils, Error) << "ELF has no .dynsym section"; + return {}; + } + + sHdr = elfSection(elf, eHdr, dynsym->sh_link); + if (!sHdr) + return {}; + off_t dynsym_nameoff = sHdr->sh_offset; + + /* Locate symbol in the .dynsym section. */ + const ElfW(Sym) *targetSymbol = nullptr; + unsigned int dynsym_num = dynsym->sh_size / dynsym->sh_entsize; + for (unsigned int i = 0; i < dynsym_num; i++) { + off_t offset = dynsym->sh_offset + dynsym->sh_entsize * i; + const ElfW(Sym) *sym = elfPointer(elf, offset); + if (!sym) + return {}; + + offset = dynsym_nameoff + sym->st_name; + const char *name = elfPointer(elf, offset, + strlen(symbol) + 1); + if (!name) + return {}; + + if (!strcmp(name, symbol) && + sym->st_info & STB_GLOBAL) { + targetSymbol = sym; + break; + } + } + + if (targetSymbol == nullptr) { + LOG(Utils, Error) << "Symbol " << symbol << " not found"; + return {}; + } + + /* Locate and return data of symbol. */ + sHdr = elfSection(elf, eHdr, targetSymbol->st_shndx); + if (!sHdr) + return {}; + off_t offset = sHdr->sh_offset + (targetSymbol->st_value - sHdr->sh_addr); + const uint8_t *data = elfPointer(elf, offset, + targetSymbol->st_size); + if (!data) + return {}; + + return { data, targetSymbol->st_size }; +} + + } /* namespace utils */ } /* namespace libcamera */ From patchwork Thu Jul 3 11:42:18 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23733 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7AE57BDCBF for ; Thu, 3 Jul 2025 11:42:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2F50868E54; Thu, 3 Jul 2025 13:42:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SXzuucbY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E087C61528 for ; Thu, 3 Jul 2025 13:42:49 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BBF32667; Thu, 3 Jul 2025 13:42:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542946; bh=C85IeLpMU0FEG7CN4qC1ruQ/QkkK44RIJFZq+KPZ5Uk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SXzuucbYmc7yg1ozCs/GsToAVAkRhxe1voltbncvwz8Ec+HPOeU/5G3bU5lwt8qDr O1BnYBKT3TsP6yNt/eKn2BLlXmPz7CZigXHepSUtt5AVSKyXA/7jHYbO97yCD1LNDS QoDok4gC5+y4wa03Cinh7GK3NYuiasEgPfXLyUjA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 3/8] libcamera: camera: Add indirection to Camera signal emissions Date: Thu, 3 Jul 2025 20:42:18 +0900 Message-ID: <20250703114225.2074071-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an extra level of indirection when emitting signals from the Camera. This is to facilitate the implementation of the layer system in the near future, which will need to hook into Camera signal emissions. Signed-off-by: Paul Elder --- No change in v2 --- include/libcamera/internal/camera.h | 4 ++++ src/libcamera/camera.cpp | 23 +++++++++++++++++++++-- src/libcamera/pipeline_handler.cpp | 2 +- src/libcamera/request.cpp | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 18f5c32a18e4..967d4e1693ec 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -35,6 +35,10 @@ public: PipelineHandler *pipe() { return pipe_.get(); } const PipelineHandler *pipe() const { return pipe_.get(); } + void emitBufferCompleted(Request *request, FrameBuffer *buffer); + void emitRequestCompleted(Request *request); + void emitDisconnected(); + std::list queuedRequests_; ControlInfoMap controlInfo_; ControlList properties_; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index c180a5fdde93..c3e656cab671 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -737,6 +737,25 @@ void Camera::Private::setState(State state) { state_.store(state, std::memory_order_release); } + +void Camera::Private::emitBufferCompleted(Request *request, FrameBuffer *buffer) +{ + Camera *camera = _o(); + camera->bufferCompleted.emit(request, buffer); +} + +void Camera::Private::emitRequestCompleted(Request *request) +{ + Camera *camera = _o(); + camera->requestCompleted.emit(request); +} + +void Camera::Private::emitDisconnected() +{ + Camera *camera = _o(); + camera->disconnected.emit(); +} + #endif /* __DOXYGEN_PUBLIC__ */ /** @@ -947,7 +966,7 @@ void Camera::disconnect() LOG(Camera, Debug) << "Disconnecting camera " << id(); _d()->disconnect(); - disconnected.emit(); + _d()->emitDisconnected(); } int Camera::exportFrameBuffers(Stream *stream, @@ -1451,7 +1470,7 @@ void Camera::requestComplete(Request *request) true)) LOG(Camera, Fatal) << "Trying to complete a request when stopped"; - requestCompleted.emit(request); + _d()->emitRequestCompleted(request); } } /* namespace libcamera */ diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index d84dff3c9f19..b5faaae08d4c 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -527,7 +527,7 @@ void PipelineHandler::doQueueRequests() bool PipelineHandler::completeBuffer(Request *request, FrameBuffer *buffer) { Camera *camera = request->_d()->camera(); - camera->bufferCompleted.emit(request, buffer); + camera->_d()->emitBufferCompleted(request, buffer); return request->_d()->completeBuffer(buffer); } diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 7f1e11e8f913..b4ae0f41a34c 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -137,7 +137,7 @@ void Request::Private::doCancelRequest() for (FrameBuffer *buffer : pending_) { buffer->_d()->cancel(); - camera_->bufferCompleted.emit(request, buffer); + camera_->_d()->emitBufferCompleted(request, buffer); } cancelled_ = true; From patchwork Thu Jul 3 11:42:19 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23734 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 80A5EC3293 for ; Thu, 3 Jul 2025 11:42:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DDF3468E59; Thu, 3 Jul 2025 13:42:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ryKJzJ1S"; dkim-atps=neutral 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 1BBF068E41 for ; Thu, 3 Jul 2025 13:42:52 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B5C7E593; Thu, 3 Jul 2025 13:42:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542948; bh=LB4Fi+5TVnj5LfL7dTH97XCzPLl0UKvl9mZCQw88JsE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ryKJzJ1SolvyvF1bsHTU0YyGXIRJNsfjOQE9bltMztoQsWbmytqhhNWAUfOI4jnWM nhlZb3zTNMVjnaQtg+4jfqS0nvCJCXaJWYFgpj61x2SK+qfOKp+gywROMKe6GmJLX/ Tj/eAkkNse9ZnfrFEoVcz8nqkpzaPRBjGIYWBNJA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 4/8] libcamera: layer_manager: Add LayerManager implementation Date: Thu, 3 Jul 2025 20:42:19 +0900 Message-ID: <20250703114225.2074071-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" We want to be able to implement layers in libcamera, which conceptually sit in between the Camera class and the application. This can be useful for implementing things that don't belong inside the Camera/IPA nor inside the application, such as intercepting and translation the AeEnable control, or implementing the Sync algorithm. To achieve this, first add a LayerManager implementation, which searches for and loads layers from shared object files, and orchestrates executing them. Actually calling into these functions from the Camera class will be added in the following patch. Signed-off-by: Paul Elder --- Changes in v2: - add closures to the layer interface - separate Layer into LayerInfo and LayerInterface, to separate out layer data and the vtable - remove generateConfiguration() and streams() from the layer interface - add init() and terminate() to the layer interface, and make them required (mainly for preparing and destroying the closure) - make LayerLoaded automatically dlclose on deconstruction, remove copy, and implement move constructors - cache controls and properties in the LayerManager --- include/libcamera/internal/layer_manager.h | 117 +++++++ include/libcamera/internal/meson.build | 1 + include/libcamera/layer.h | 54 +++ include/libcamera/meson.build | 1 + src/layer/meson.build | 10 + src/libcamera/layer_manager.cpp | 383 +++++++++++++++++++++ src/libcamera/meson.build | 1 + src/meson.build | 1 + 8 files changed, 568 insertions(+) create mode 100644 include/libcamera/internal/layer_manager.h create mode 100644 include/libcamera/layer.h create mode 100644 src/layer/meson.build create mode 100644 src/libcamera/layer_manager.cpp diff --git a/include/libcamera/internal/layer_manager.h b/include/libcamera/internal/layer_manager.h new file mode 100644 index 000000000000..0d108bcddf3d --- /dev/null +++ b/include/libcamera/internal/layer_manager.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer manager interface + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LayerManager) + +class LayerManager +{ +public: + LayerManager(); + ~LayerManager() = default; + + void init(const Camera *camera, const ControlList &properties, + const ControlInfoMap &controlInfoMap); + void terminate(const Camera *camera); + + void bufferCompleted(const Camera *camera, + Request *request, FrameBuffer *buffer); + void requestCompleted(const Camera *camera, Request *request); + void disconnected(const Camera *camera); + + void acquire(const Camera *camera); + void release(const Camera *camera); + + const ControlInfoMap &controls(const Camera *camera) const { return controls_.at(camera); } + const ControlList &properties(const Camera *camera) const { return properties_.at(camera); } + + void configure(const Camera *camera, const CameraConfiguration *config, + const ControlInfoMap &controlInfoMap); + + void createRequest(const Camera *camera, + uint64_t cookie, const Request *request); + + void queueRequest(const Camera *camera, Request *request); + + void start(const Camera *camera, const ControlList *controls); + void stop(const Camera *camera); + +private: + /* Extend the layer with information specific to load-handling */ + struct LayerLoaded + { + LayerLoaded() + : info(nullptr), vtable(nullptr), dlHandle(nullptr) + { + } + + LayerLoaded(LayerLoaded &&other) + : info(other.info), vtable(other.vtable), + dlHandle(other.dlHandle) + { + other.dlHandle = nullptr; + } + + LayerLoaded &operator=(LayerLoaded &&other) + { + info = other.info; + vtable = other.vtable; + dlHandle = other.dlHandle; + other.dlHandle = nullptr; + return *this; + } + + ~LayerLoaded() + { + if (dlHandle) + dlclose(dlHandle); + } + + LayerInfo *info; + LayerInterface *vtable; + void *dlHandle; + + private: + LIBCAMERA_DISABLE_COPY(LayerLoaded) + }; + + using ClosureKey = std::tuple; + + void updateProperties(const Camera *camera, + const ControlList &properties); + void updateControls(const Camera *camera, + const ControlInfoMap &controlInfoMap); + + LayerLoaded createLayer(const std::string &file); + std::deque executionQueue_; + std::map closures_; + + std::map controls_; + std::map properties_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 690f5c5ec9f6..20e6c295601f 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -29,6 +29,7 @@ libcamera_internal_headers = files([ 'ipa_proxy.h', 'ipc_pipe.h', 'ipc_unixsocket.h', + 'layer_manager.h', 'mapped_framebuffer.h', 'matrix.h', 'media_device.h', diff --git a/include/libcamera/layer.h b/include/libcamera/layer.h new file mode 100644 index 000000000000..cd0e26a3b72b --- /dev/null +++ b/include/libcamera/layer.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer interface + */ + +#pragma once + +#include +#include + +#include + +#include + +namespace libcamera { + +class CameraConfiguration; +class FrameBuffer; +class Request; +class Stream; +enum class StreamRole; + +struct LayerInfo { + const char *name; + int layerAPIVersion; +}; + +struct LayerInterface { + void *(*init)(const std::string &id); + void (*terminate)(void *); + + void (*bufferCompleted)(void *, Request *, FrameBuffer *); + void (*requestCompleted)(void *, Request *); + void (*disconnected)(void *); + + void (*acquire)(void *); + void (*release)(void *); + + ControlInfoMap::Map (*controls)(void *, ControlInfoMap &); + ControlList (*properties)(void *, ControlList &); + + void (*configure)(void *, const CameraConfiguration *); + + void (*createRequest)(void *, uint64_t, const Request *); + + void (*queueRequest)(void *, Request *); + + void (*start)(void *, const ControlList *); + void (*stop)(void *); +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index 30ea76f9470a..552af112abb5 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -11,6 +11,7 @@ libcamera_public_headers = files([ 'framebuffer.h', 'framebuffer_allocator.h', 'geometry.h', + 'layer.h', 'logging.h', 'orientation.h', 'pixel_format.h', diff --git a/src/layer/meson.build b/src/layer/meson.build new file mode 100644 index 000000000000..dee5e5ac5804 --- /dev/null +++ b/src/layer/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_includes = [ + libcamera_includes, +] + +layer_install_dir = libcamera_libdir / 'layers' + +config_h.set('LAYER_DIR', + '"' + get_option('prefix') / layer_install_dir + '"') diff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp new file mode 100644 index 000000000000..d707d4e12a53 --- /dev/null +++ b/src/libcamera/layer_manager.cpp @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer manager + */ + +#include "libcamera/internal/layer_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/utils.h" + +/** + * \file layer_manager.h + * \brief Layer manager + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LayerManager) + +/** + * \class LayerManager + * \brief Layer manager + * + * The Layer manager discovers layer implementations from disk, and creates + * execution queues for every function that is implemented by each layer and + * executes them. A layer is a layer that sits between libcamera and the + * application, and hooks into the public Camera interface. + */ + +/** + * \brief Construct a LayerManager instance + * + * The LayerManager class is meant be instantiated by the Camera. + */ +LayerManager::LayerManager() +{ + std::map layers; + + /* \todo Implement built-in layers */ + + /* This returns the number of "modules" successfully loaded */ + std::function addDirHandler = + [this, &layers](const std::string &file) { + LayerManager::LayerLoaded layer = createLayer(file); + if (!layer.info) + return 0; + + LOG(LayerManager, Debug) << "Loaded layer '" << file << "'"; + + layers.emplace(std::string(layer.info->name), std::move(layer)); + + return 1; + }; + + /* User-specified paths take precedence. */ + /* \todo Document this */ + const char *layerPaths = utils::secure_getenv("LIBCAMERA_LAYER_PATH"); + if (layerPaths) { + for (const auto &dir : utils::split(layerPaths, ":")) { + if (dir.empty()) + continue; + + /* + * \todo Move the shared objects into one directory + * instead of each in their own subdir + */ + utils::addDir(dir.c_str(), 1, addDirHandler); + } + } + + /* + * When libcamera is used before it is installed, load layers from the + * same build directory as the libcamera library itself. + */ + std::string root = utils::libcameraBuildPath(); + if (!root.empty()) { + std::string layerBuildPath = root + "src/layer"; + constexpr int maxDepth = 2; + + LOG(LayerManager, Info) + << "libcamera is not installed. Adding '" + << layerBuildPath << "' to the layer search path"; + + utils::addDir(layerBuildPath.c_str(), maxDepth, addDirHandler); + } + + /* Finally try to load layers from the installed system path. */ + utils::addDir(LAYER_DIR, 1, addDirHandler); + + /* Order the layers */ + /* \todo Document this. First is closer to application, last is closer to libcamera */ + const char *layerList = utils::secure_getenv("LIBCAMERA_LAYERS_ENABLE"); + if (layerList) { + for (const auto &layerName : utils::split(layerList, ":")) { + if (layerName.empty()) + continue; + + const auto &it = layers.find(layerName); + if (it == layers.end()) + continue; + + executionQueue_.push_back(std::move(it->second)); + } + } +} + +void LayerManager::init(const Camera *camera, const ControlList &properties, + const ControlInfoMap &controlInfoMap) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + void *closure = layer.vtable->init(camera->id()); + closures_[std::make_tuple(camera, &layer)] = closure; + } + + /* + * We need to iterate over the layers individually to merge all of + * their controls, so we'll factor out updateControls() as it needs to be + * run again at configure(). + */ + updateProperties(camera, properties); + updateControls(camera, controlInfoMap); +} + +void LayerManager::terminate(const Camera *camera) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->terminate(closure); + } +} + +LayerManager::LayerLoaded LayerManager::createLayer(const std::string &filename) +{ + LayerLoaded layer; + + File file{ filename }; + if (!file.open(File::OpenModeFlag::ReadOnly)) { + LOG(LayerManager, Error) << "Failed to open layer: " + << strerror(-file.error()); + return layer; + } + + Span data = file.map(); + int ret = utils::elfVerifyIdent(data); + if (ret) { + LOG(LayerManager, Error) << "Layer is not an ELF file"; + return layer; + } + + Span info = utils::elfLoadSymbol(data, "layerInfo"); + if (info.size() < sizeof(LayerInfo)) { + LOG(LayerManager, Error) << "Layer has no valid info"; + return layer; + } + + void *dlHandle = dlopen(file.fileName().c_str(), RTLD_LAZY); + if (!dlHandle) { + LOG(LayerManager, Error) + << "Failed to open layer shared object: " + << dlerror(); + return layer; + } + + void *layerInfo = dlsym(dlHandle, "layerInfo"); + if (!layerInfo) { + LOG(LayerManager, Error) + << "Failed to load layerInfo from layer shared object: " + << dlerror(); + dlclose(dlHandle); + return layer; + } + + void *vtable = dlsym(dlHandle, "layerInterface"); + if (!vtable) { + LOG(LayerManager, Error) + << "Failed to load layerInterface from layer shared object: " + << dlerror(); + dlclose(dlHandle); + return layer; + } + + layer.info = static_cast(layerInfo); + layer.vtable = static_cast(vtable); + layer.dlHandle = dlHandle; + + /* + * No need to dlclose after this as the LayerLoaded deconstructor will + * handle it + */ + + /* \todo Implement this. It should come from the libcamera version */ + if (layer.info->layerAPIVersion != 1) { + LOG(LayerManager, Error) << "Layer API version mismatch"; + layer.info = nullptr; + return layer; + } + + /* \todo Document these requirements */ + if (!layer.vtable->init) { + LOG(LayerManager, Error) << "Layer doesn't implement init"; + layer.info = nullptr; + return layer; + } + + /* \todo Document these requirements */ + if (!layer.vtable->terminate) { + LOG(LayerManager, Error) << "Layer doesn't implement terminate"; + layer.info = nullptr; + return layer; + } + + /* \todo Validate the layer name. */ + + return layer; +} + +void LayerManager::bufferCompleted(const Camera *camera, Request *request, FrameBuffer *buffer) +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + if ((*it).vtable->bufferCompleted) { + void *closure = closures_.at(std::make_tuple(camera, &(*it))); + (*it).vtable->bufferCompleted(closure, request, buffer); + } + } +} + +void LayerManager::requestCompleted(const Camera *camera, Request *request) +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + if ((*it).vtable->requestCompleted) { + void *closure = closures_.at(std::make_tuple(camera, &(*it))); + (*it).vtable->requestCompleted(closure, request); + } + } +} + +void LayerManager::disconnected(const Camera *camera) +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + if ((*it).vtable->disconnected) { + void *closure = closures_.at(std::make_tuple(camera, &(*it))); + (*it).vtable->disconnected(closure); + } + } +} + +void LayerManager::acquire(const Camera *camera) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->acquire) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->acquire(closure); + } + } +} + +void LayerManager::release(const Camera *camera) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->release) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->release(closure); + } + } +} + +void LayerManager::updateProperties(const Camera *camera, + const ControlList &properties) +{ + ControlList props = properties; + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->properties) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + ControlList ret = layer.vtable->properties(closure, props); + props.merge(ret, ControlList::MergePolicy::OverwriteExisting); + } + } + properties_[camera] = props; +} + +void LayerManager::updateControls(const Camera *camera, + const ControlInfoMap &controlInfoMap) +{ + ControlInfoMap infoMap = controlInfoMap; + /* \todo Simplify this once ControlInfoMaps become easier to modify */ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->controls) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + ControlInfoMap::Map ret = layer.vtable->controls(closure, infoMap); + ControlInfoMap::Map map; + /* Merge the layer's ret later so that layers can overwrite */ + for (auto &pair : infoMap) + map.insert(pair); + for (auto &pair : ret) + map.insert(pair); + infoMap = ControlInfoMap(std::move(map), + libcamera::controls::controls); + } + } + controls_[camera] = infoMap; +} + +void LayerManager::configure(const Camera *camera, + const CameraConfiguration *config, + const ControlInfoMap &controlInfoMap) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->configure) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->configure(closure, config); + } + } + + updateControls(camera, controlInfoMap); +} + +void LayerManager::createRequest(const Camera *camera, uint64_t cookie, const Request *request) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->createRequest) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->createRequest(closure, cookie, request); + } + } +} + +void LayerManager::queueRequest(const Camera *camera, Request *request) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->queueRequest) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->queueRequest(closure, request); + } + } +} + +void LayerManager::start(const Camera *camera, const ControlList *controls) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->start) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->start(closure, controls); + } + } +} + +void LayerManager::stop(const Camera *camera) +{ + for (LayerManager::LayerLoaded &layer : executionQueue_) { + if (layer.vtable->stop) { + void *closure = closures_.at(std::make_tuple(camera, &layer)); + layer.vtable->stop(closure); + } + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 6a71b2903d27..0c2086a8399c 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -40,6 +40,7 @@ libcamera_internal_sources = files([ 'ipc_pipe.cpp', 'ipc_pipe_unixsocket.cpp', 'ipc_unixsocket.cpp', + 'layer_manager.cpp', 'mapped_framebuffer.cpp', 'matrix.cpp', 'media_device.cpp', diff --git a/src/meson.build b/src/meson.build index 8eb8f05b362f..37368b01cbf2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -63,6 +63,7 @@ subdir('libcamera') subdir('android') subdir('ipa') +subdir('layer') subdir('apps') From patchwork Thu Jul 3 11:42:20 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23735 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 64249BDCBF for ; Thu, 3 Jul 2025 11:42:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0D95C68E53; Thu, 3 Jul 2025 13:42:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gao/JnoJ"; dkim-atps=neutral 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 24B4468E5C for ; Thu, 3 Jul 2025 13:42:54 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E6245667; Thu, 3 Jul 2025 13:42:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542950; bh=I32D9UINd7c7S7BdrRsz7Y9DNTN1rCnQRHfaIqpBB64=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gao/JnoJDOTRmQE+B75MfW4NbVGF9sxEVct9D9gUB3WC2rkz7+083pZm8XIbEnbwy LEwmdEoYp/BupppjEuNtIv3M+u/HIasDUAs1vXDkZ7wmuX+zSls+swC/sVQDRyT1hd aT+BoP4eO7JTgTcJkpoTpfjw8GAyZOZzFSkMrICc= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 5/8] libcamera: camera_manager: Add LayerManager Date: Thu, 3 Jul 2025 20:42:20 +0900 Message-ID: <20250703114225.2074071-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add to the CameraManager a LayerManager member. This allows us to have one LayerManager that handles loading all the layer shared objects, and then each Camera can access the layer via the LayerManager via through the CameraManager. Signed-off-by: Paul Elder --- New in v2 --- include/libcamera/internal/camera_manager.h | 3 +++ src/libcamera/camera_manager.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h index 0150ca61f7de..ef5b0688de23 100644 --- a/include/libcamera/internal/camera_manager.h +++ b/include/libcamera/internal/camera_manager.h @@ -25,6 +25,7 @@ namespace libcamera { class Camera; class DeviceEnumerator; class IPAManager; +class LayerManager; class PipelineHandlerFactoryBase; class CameraManager::Private : public Extensible::Private, public Thread @@ -39,6 +40,7 @@ public: void removeCamera(std::shared_ptr camera) LIBCAMERA_TSA_EXCLUDES(mutex_); IPAManager *ipaManager() const { return ipaManager_.get(); } + LayerManager *layerManager() const { return layerManager_.get(); } protected: void run() override; @@ -66,6 +68,7 @@ private: std::unique_ptr ipaManager_; ProcessManager processManager_; + std::unique_ptr layerManager_; }; } /* namespace libcamera */ diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index e62e7193cfdc..0f202fc6314e 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -15,6 +15,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/layer_manager.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/pipeline_handler.h" @@ -41,6 +42,7 @@ CameraManager::Private::Private() : initialized_(false) { ipaManager_ = std::make_unique(); + layerManager_ = std::make_unique(); } int CameraManager::Private::start() From patchwork Thu Jul 3 11:42:21 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23736 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E67C9C3293 for ; Thu, 3 Jul 2025 11:42:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9B29468E64; Thu, 3 Jul 2025 13:42:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K1IVA8ln"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0D46468E52 for ; Thu, 3 Jul 2025 13:42:56 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E340782E; Thu, 3 Jul 2025 13:42:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542952; bh=kC7XA7hFK5mk28YAu8uyaO9xVQYEwDVa+a8Yh/1qIvw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K1IVA8lngWNWk+KybPc5cX+fQLguMtXD3BhUc5YsjLFlu0DVBwgg3XUZCuX4m+9EN tNtI93412YSe5riP/ZVLI1g2UzXk+lFSMIzOH781rOE/T0kXdW4GQCx2xz7PAeQdZ3 TiDCQYZY+tQJBW4hwl9IQ66Eu9UjsMQcdaEFSnsw= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 6/8] libcamera: camera: Hook into the LayerManager Date: Thu, 3 Jul 2025 20:42:21 +0900 Message-ID: <20250703114225.2074071-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add hooks into the CameraManager to call into the LayerManager on all relevant public-facing interface of Camera. The entry point for each function into the LayerManager is has been chosen based on the capabilities that we want to allow to Layer implementations. Signed-off-by: Paul Elder --- Changes in v2: - move the LayerManager out of Camera into the CameraManager - remove hooks for generateConfiguration() and streams() - Camera now passes itself into the layer hooks, as it is required for the LayerManager to fetch the closure based on the camera to pass to each layer --- src/libcamera/camera.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index c3e656cab671..665fc5157349 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_controls.h" +#include "libcamera/internal/camera_manager.h" +#include "libcamera/internal/layer_manager.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" @@ -741,18 +744,24 @@ void Camera::Private::setState(State state) void Camera::Private::emitBufferCompleted(Request *request, FrameBuffer *buffer) { Camera *camera = _o(); + LayerManager *layerManager = pipe()->cameraManager()->_d()->layerManager(); + layerManager->bufferCompleted(camera, request, buffer); camera->bufferCompleted.emit(request, buffer); } void Camera::Private::emitRequestCompleted(Request *request) { Camera *camera = _o(); + LayerManager *layerManager = pipe()->cameraManager()->_d()->layerManager(); + layerManager->requestCompleted(camera, request); camera->requestCompleted.emit(request); } void Camera::Private::emitDisconnected() { Camera *camera = _o(); + LayerManager *layerManager = pipe()->cameraManager()->_d()->layerManager(); + layerManager->disconnected(camera); camera->disconnected.emit(); } @@ -943,10 +952,14 @@ Camera::Camera(std::unique_ptr d, const std::string &id, _d()->id_ = id; _d()->streams_ = streams; _d()->validator_ = std::make_unique(this); + _d()->pipe()->cameraManager()->_d()->layerManager()->init(this, + _d()->properties_, + _d()->controlInfo_); } Camera::~Camera() { + _d()->pipe()->cameraManager()->_d()->layerManager()->terminate(this); } /** @@ -1032,6 +1045,9 @@ int Camera::acquire() return -EBUSY; } + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->acquire(this); + d->setState(Private::CameraAcquired); return 0; @@ -1064,6 +1080,9 @@ int Camera::release() d->pipe_->invokeMethod(&PipelineHandler::release, ConnectionTypeBlocking, this); + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->release(this); + d->setState(Private::CameraAvailable); return 0; @@ -1081,7 +1100,8 @@ int Camera::release() */ const ControlInfoMap &Camera::controls() const { - return _d()->controlInfo_; + LayerManager *layerManager = _d()->pipe()->cameraManager()->_d()->layerManager(); + return layerManager->controls(this); } /** @@ -1094,7 +1114,8 @@ const ControlInfoMap &Camera::controls() const */ const ControlList &Camera::properties() const { - return _d()->properties_; + LayerManager *layerManager = _d()->pipe()->cameraManager()->_d()->layerManager(); + return layerManager->properties(this); } /** @@ -1242,6 +1263,9 @@ int Camera::configure(CameraConfiguration *config) d->activeStreams_.insert(stream); } + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->configure(this, config, d->controlInfo_); + d->setState(Private::CameraConfigured); return 0; @@ -1282,6 +1306,9 @@ std::unique_ptr Camera::createRequest(uint64_t cookie) /* Associate the request with the pipeline handler. */ d->pipe_->registerRequest(request.get()); + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->createRequest(this, cookie, request.get()); + return request; } @@ -1366,6 +1393,9 @@ int Camera::queueRequest(Request *request) } } + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->queueRequest(this, request); + d->pipe_->invokeMethod(&PipelineHandler::queueRequest, ConnectionTypeQueued, request); @@ -1402,6 +1432,9 @@ int Camera::start(const ControlList *controls) ASSERT(d->requestSequence_ == 0); + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->start(this, controls); + ret = d->pipe_->invokeMethod(&PipelineHandler::start, ConnectionTypeBlocking, this, controls); if (ret) @@ -1446,6 +1479,9 @@ int Camera::stop() d->setState(Private::CameraStopping); + LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); + layerManager->stop(this); + d->pipe_->invokeMethod(&PipelineHandler::stop, ConnectionTypeBlocking, this); From patchwork Thu Jul 3 11:42:22 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23737 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id C8BA5BDCBF for ; Thu, 3 Jul 2025 11:42:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7071868E62; Thu, 3 Jul 2025 13:42:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uqk6L9MC"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0FEF268E66 for ; Thu, 3 Jul 2025 13:42:58 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D9007593; Thu, 3 Jul 2025 13:42:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542954; bh=pWJSjtrAFC6QKkA3NWNaXZsgb0xLZHDvLnzNoU29evY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uqk6L9MCjWBNX5gVhCMCUK+m89nc4ETDQFyScN1UXYvomQ3QRLQKYKk4iUMjXdj4N gUMFhZ7pBwBBMUqyxo5ePzSbdEU9/JKKYRriXJb8yC7QPDInUzDqPUCGp3Wd6hf8I0 Ecz9ZYB8GUDPI5QqmCTffzMeT8jFNh8lDan7d4c4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 7/8] layer: Add layer to inject AeEnable control Date: Thu, 3 Jul 2025 20:42:22 +0900 Message-ID: <20250703114225.2074071-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a layer to implement the AeEnable control, so that we can remove it from all IPAs and the Camera class, so that it is transparent to all parties and it is available for applications to use. Signed-off-by: Paul Elder --- For layers like this that we forsee to be "always built-in" we might want a way to build it into libcamera instead of into separate shared object files. In any case, this also serves as a demo of how I envision a layer implementation to work. Changes in v2: - add init() and terminate() - use closures - remove layer namespacing - add gnu_symbol_visibility --- src/layer/inject_controls/inject_controls.cpp | 176 ++++++++++++++++++ src/layer/inject_controls/inject_controls.h | 24 +++ src/layer/inject_controls/meson.build | 16 ++ src/layer/meson.build | 2 + 4 files changed, 218 insertions(+) create mode 100644 src/layer/inject_controls/inject_controls.cpp create mode 100644 src/layer/inject_controls/inject_controls.h create mode 100644 src/layer/inject_controls/meson.build diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp new file mode 100644 index 000000000000..f12faff8318c --- /dev/null +++ b/src/layer/inject_controls/inject_controls.cpp @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#include "inject_controls.h" + +#include +#include + +#include +#include +#include +#include + +void *init([[maybe_unused]] const std::string &id) +{ + InjectControls *closure = new InjectControls; + *closure = {}; + return static_cast(closure); +} + +void terminate(void *closure) +{ + InjectControls *obj = static_cast(closure); + delete obj; +} + +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls) +{ + InjectControls *obj = static_cast(closure); + + auto it = ctrls.find(&libcamera::controls::ExposureTimeMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto)) + obj->aeAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual)) + obj->meAvailable = true; + } + } + + it = ctrls.find(&libcamera::controls::AnalogueGainMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto)) + obj->agAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual)) + obj->mgAvailable = true; + } + } + + std::set values; + if (obj->aeAvailable || obj->agAvailable) + values.insert(true); + if (obj->meAvailable || obj->mgAvailable) + values.insert(false); + + if (values.empty()) + return {}; + + if (values.size() == 1) { + bool value = *values.begin(); + return { { &libcamera::controls::AeEnable, + libcamera::ControlInfo(value, value, value) } }; + } + + return { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } }; +} + +void queueRequest(void *closure, libcamera::Request *request) +{ + InjectControls *obj = static_cast(closure); + + libcamera::ControlList &ctrls = request->controls(); + auto aeEnable = ctrls.get(libcamera::controls::AeEnable); + if (!aeEnable) + return; + + if (*aeEnable) { + if (obj->aeAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeAuto); + } + + if (obj->agAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeAuto); + } + } else { + if (obj->meAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeManual); + } + + if (obj->mgAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeManual); + } + } +} + +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request) +{ + libcamera::ControlList &metadata = request->metadata(); + + auto eMode = metadata.get(libcamera::controls::ExposureTimeMode); + auto aMode = metadata.get(libcamera::controls::AnalogueGainMode); + + if (!eMode && !aMode) + return; + + bool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto; + bool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual; + bool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto; + bool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual; + + /* Exposure time not reported at all; use gain only */ + if (!ae && !me) { + metadata.set(libcamera::controls::AeEnable, ag); + return; + } + + /* Analogue gain not reported at all; use exposure time only */ + if (!ag && !mg) { + metadata.set(libcamera::controls::AeEnable, ae); + return; + } + + /* + * Gain mode and exposure mode are not equal; therefore at least one is + * manual, so set AeEnable to false + */ + if (ag != ae) { + metadata.set(libcamera::controls::AeEnable, false); + return; + } + + /* ag and ae are equal, so just choose one */ + metadata.set(libcamera::controls::AeEnable, ag); + return; +} + +namespace libcamera { + +extern "C" { + +[[gnu::visibility("default")]] +struct LayerInfo layerInfo{ + .name = "inject_controls", + .layerAPIVersion = 1, +}; + +[[gnu::visibility("default")]] +struct LayerInterface layerInterface{ + .init = init, + .terminate = terminate, + .bufferCompleted = nullptr, + .requestCompleted = requestCompleted, + .disconnected = nullptr, + .acquire = nullptr, + .release = nullptr, + .controls = updateControls, + .properties = nullptr, + .configure = nullptr, + .createRequest = nullptr, + .queueRequest = queueRequest, + .start = nullptr, + .stop = nullptr, +}; + +} + +} /* namespace libcamera */ diff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h new file mode 100644 index 000000000000..42c094d7f76a --- /dev/null +++ b/src/layer/inject_controls/inject_controls.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#pragma once + +#include +#include + +struct InjectControls { + bool aeAvailable; + bool meAvailable; + bool agAvailable; + bool mgAvailable; +}; + +void *init(const std::string &id); +void terminate(void *closure); +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls); +void queueRequest(void *closure, libcamera::Request *request); +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request); diff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build new file mode 100644 index 000000000000..d1e402746990 --- /dev/null +++ b/src/layer/inject_controls/meson.build @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_name = 'layer_inject_controls' + +layer_inject_sources = files([ + 'inject_controls.h', + 'inject_controls.cpp', +]) + +mod = shared_module(layer_name, layer_inject_sources, + name_prefix : '', + include_directories : [layer_includes], + dependencies : libcamera_public, + gnu_symbol_visibility: 'hidden', + install : true, + install_dir : layer_install_dir) diff --git a/src/layer/meson.build b/src/layer/meson.build index dee5e5ac5804..d5793f8c4525 100644 --- a/src/layer/meson.build +++ b/src/layer/meson.build @@ -8,3 +8,5 @@ layer_install_dir = libcamera_libdir / 'layers' config_h.set('LAYER_DIR', '"' + get_option('prefix') / layer_install_dir + '"') + +subdir('inject_controls') From patchwork Thu Jul 3 11:42:23 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23738 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 80D0DBDCBF for ; Thu, 3 Jul 2025 11:43:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 380F268E67; Thu, 3 Jul 2025 13:43:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ggCj3sXd"; dkim-atps=neutral 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 EA94568E67 for ; Thu, 3 Jul 2025 13:42:59 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:c61b:f3bf:2578:6674]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CE7AA778; Thu, 3 Jul 2025 13:42:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1751542956; bh=djq26Se4Ng4ZpJjwkLtOfQqV0KW71WiM2/wZITXtsEc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ggCj3sXdUTBd8yJ8fzVLFVlvzfapTCgIVSC8Q6V/JpsIX1fj/tSq4EyzC4qooAkrf F1RCSm0Ec6N1guELTpKKlovg9ubfi7D5EjcrNjixK1o5gWLWZTf5EHC7gRKz4kVFPo bOrGn0LQLUpFP5YZwPh+HcbDNPt4EdzCqRhAXoEY= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v2 8/8] camera, ipa: all: Remove AeEnable handling Date: Thu, 3 Jul 2025 20:42:23 +0900 Message-ID: <20250703114225.2074071-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250703114225.2074071-1-paul.elder@ideasonboard.com> References: <20250703114225.2074071-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Remove AeEnable handling from the Camera and all IPAs, as it is now handled by the inject_controls layer. Signed-off-by: Paul Elder --- The mali-c55 IPA needs to support AnalogueGainMode and ExposureTimeMode, but that is out of scope for this series. No change in v2 --- src/ipa/mali-c55/algorithms/agc.cpp | 1 + src/ipa/rkisp1/algorithms/agc.cpp | 2 -- src/ipa/rpi/common/ipa_base.cpp | 2 -- src/libcamera/camera.cpp | 19 ------------------- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 5 ----- 5 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 70667db34f1e..818cea35afa9 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -137,6 +137,7 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) if (ret) return ret; + /* \todo Support AnalogueGainMode and ExposureTimeMode */ context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); context.ctrlMap[&controls::DigitalGain] = ControlInfo( static_cast(kMinDigitalGain), diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 137a0750017b..8a890d387efb 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -155,8 +155,6 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData) ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), ControlValue(controls::AnalogueGainModeManual) } }, ControlValue(controls::AnalogueGainModeAuto)); - /* \todo Move this to the Camera class */ - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); context.ctrlMap.merge(controls()); return 0; diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index 6565f5366312..059b1bd5942e 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -55,8 +55,6 @@ constexpr Duration controllerMinFrameDuration = 1.0s / 30.0; /* List of controls handled by the Raspberry Pi IPA */ const ControlInfoMap::Map ipaControls{ - /* \todo Move this to the Camera class */ - { &controls::AeEnable, ControlInfo(false, true, true) }, { &controls::ExposureTimeMode, ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), ControlValue(controls::ExposureTimeModeManual) } }, diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 665fc5157349..3d0782a9eed2 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1374,25 +1374,6 @@ int Camera::queueRequest(Request *request) } } - /* Pre-process AeEnable. */ - ControlList &controls = request->controls(); - const auto &aeEnable = controls.get(controls::AeEnable); - if (aeEnable) { - if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) && - !controls.contains(controls::AnalogueGainMode.id())) { - controls.set(controls::AnalogueGainMode, - *aeEnable ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); - } - - if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) && - !controls.contains(controls::ExposureTimeMode.id())) { - controls.set(controls::ExposureTimeMode, - *aeEnable ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - } - } - LayerManager *layerManager = d->pipe()->cameraManager()->_d()->layerManager(); layerManager->queueRequest(this, request); diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index 4b5816dfdde0..ab12d5b5ae9a 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -607,11 +607,6 @@ int UVCCameraData::init(MediaDevice *media) addControl(cid, info, &ctrls); } - if (autoExposureMode_ && manualExposureMode_) { - /* \todo Move this to the Camera class */ - ctrls[&controls::AeEnable] = ControlInfo(false, true, true); - } - controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); /*