From patchwork Thu Jan 29 08:28:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26029 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 6D8BEC3226 for ; Thu, 29 Jan 2026 08:28:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C1A1261FD5; Thu, 29 Jan 2026 09:28:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="D9CcUl1v"; 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 1769E61FC5 for ; Thu, 29 Jan 2026 09:28:26 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 99B121661; Thu, 29 Jan 2026 09:27:47 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675268; bh=NIy/w9Kpz3z+X2wA2llmgDi4mv7PpC3Pl9bOqQmyXVg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=D9CcUl1vhQnTGF+rAuyJDj3Y33r/Wnxn9nQY4aB911HJ9YhQ5RdamNmEECE4tMQbm 5BdD0DXHL0lp6UryDgucYz4vPAM4clYIuddbE1tWV3z8FwYeVVTOOMBOSXgJ7i8J3b Q7Km+YUPAUEZ4OCY2f9YH38bVamo63YGfeZxgopI= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 1/8] libcamera: ipa_manager: Factor out .so file searching Date: Thu, 29 Jan 2026 17:28:07 +0900 Message-ID: <20260129082814.1777779-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 v6 No change in v5 No change in v4 Changes in v3: - rename addDir to findSharedObjects - make parseDir static No change in v2 --- include/libcamera/internal/ipa_manager.h | 4 - include/libcamera/internal/meson.build | 1 + include/libcamera/internal/utils.h | 23 +++++ src/libcamera/ipa_manager.cpp | 107 ++++------------------- src/libcamera/meson.build | 1 + src/libcamera/utils.cpp | 107 +++++++++++++++++++++++ 6 files changed, 151 insertions(+), 92 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 f8ce780169e6..dcfcf8d69da5 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -68,10 +68,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 4d2a09bd7041..ed145a6dd7cd 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -43,6 +43,7 @@ libcamera_internal_headers = files([ 'shared_mem_object.h', 'source_paths.h', 'sysfs.h', + 'utils.h', 'v4l2_device.h', 'v4l2_pixelformat.h', 'v4l2_request.h', diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h new file mode 100644 index 000000000000..5b7957b5d77e --- /dev/null +++ b/include/libcamera/internal/utils.h @@ -0,0 +1,23 @@ +/* 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 { + +unsigned int findSharedObjects(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 35171d097136..244a8ee67f05 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -8,7 +8,7 @@ #include "libcamera/internal/ipa_manager.h" #include -#include +#include #include #include #include @@ -22,6 +22,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 @@ -118,6 +119,20 @@ IPAManager::IPAManager(const GlobalConfiguration &configuration) unsigned int ipaCount = 0; + auto &modules = modules_; + std::function soHandler = + [&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 auto modulePaths = configuration.envListOption( @@ -127,7 +142,7 @@ IPAManager::IPAManager(const GlobalConfiguration &configuration) if (dir.empty()) continue; - ipaCount += addDir(dir.c_str()); + ipaCount += utils::findSharedObjects(dir.c_str(), 0, soHandler); } if (!modulePaths.empty() && !ipaCount) @@ -148,11 +163,11 @@ IPAManager::IPAManager(const GlobalConfiguration &configuration) << "libcamera is not installed. Adding '" << ipaBuildPath << "' to the IPA search path"; - ipaCount += addDir(ipaBuildPath.c_str(), maxDepth); + ipaCount += utils::findSharedObjects(ipaBuildPath.c_str(), maxDepth, soHandler); } /* Finally try to load IPAs from the installed system path. */ - ipaCount += addDir(IPA_MODULE_DIR); + ipaCount += utils::findSharedObjects(IPA_MODULE_DIR, 0, soHandler); if (!ipaCount) LOG(IPAManager, Warning) @@ -161,90 +176,6 @@ IPAManager::IPAManager(const GlobalConfiguration &configuration) 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 d15943586300..186d5c48ccd5 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -52,6 +52,7 @@ libcamera_internal_sources = files([ 'shared_mem_object.cpp', 'source_paths.cpp', 'sysfs.cpp', + 'utils.cpp', 'v4l2_device.cpp', 'v4l2_pixelformat.cpp', 'v4l2_request.cpp', diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp new file mode 100644 index 000000000000..2ce62f236cea --- /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. + */ +static 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 findSharedObjects(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 Jan 29 08:28:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26030 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 9F1E2C3226 for ; Thu, 29 Jan 2026 08:28:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5B29B61FD2; Thu, 29 Jan 2026 09:28:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RQejdnNC"; 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 00DAD615B2 for ; Thu, 29 Jan 2026 09:28:28 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 44D0E6A6; Thu, 29 Jan 2026 09:27:49 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675270; bh=Lffh7FPXPMugG/HkVBKKjemIgzyKsSsqh9lb5T3P5AM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RQejdnNCrP/SnBjHkLnQUP7kKB4m0LGbyOLJX6EZTqti4fVwYBRIucvkjQKibPDSN 3CjrlEoLnvldLVHvoPfeH1oBP8dyWCch0tKnJOj6UBD+zW5UrWXIe5uGsH3e+Hq4yY TwL946yvBEJc7FHP6rLFQWek1v1iAH+bSQbBcpjQ= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Stefan Klug Subject: [PATCH v6 2/8] libcamera: ipa_module: Factor out ELF file handling Date: Thu, 29 Jan 2026 17:28:08 +0900 Message-ID: <20260129082814.1777779-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 Reviewed-by: Stefan Klug --- No change in v6 No change in v5 No change in v4 Changes in v3: - add documentation for elfVerifyIdent No change in v2 --- include/libcamera/internal/utils.h | 6 ++ src/libcamera/ipa_module.cpp | 152 +-------------------------- src/libcamera/utils.cpp | 162 +++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 149 deletions(-) diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h index 5b7957b5d77e..7d69849b1c19 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 { @@ -18,6 +21,9 @@ namespace utils { unsigned int findSharedObjects(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 2ce62f236cea..6894b489b557 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,125 @@ unsigned int findSharedObjects(const char *libDir, unsigned int maxDepth, return count; } +/** + * \brief Verify that a binary blob is an ELF + * \param[in] elf The binary blob as a Span + * + * \return 0 for success, and -ENOEXEC otherwise + */ +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 Jan 29 08:28:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26031 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 91194C3226 for ; Thu, 29 Jan 2026 08:28:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4A7BB61FCF; Thu, 29 Jan 2026 09:28:33 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lxmGy67K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 31E5961FC6 for ; Thu, 29 Jan 2026 09:28:30 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2E5AB1AED; Thu, 29 Jan 2026 09:27:50 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675272; bh=0JLjnVcYcwPLW/uhk53vTIOSM+l2ha7LNK092Powohg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lxmGy67KLfxiua+OSzZDvtanyMS2yVfjDhBrWx3sUtz5e46I9d2eoWpcoTjUXWufz 4UIo7lhVzTjAbn2Z2e+pI3PGeF1x6LcK8VdLAZFnD3AHNbG2u6oyOJ3uGJ88wrhe9G deXgD2o8dXOffRR1T5mj10Bv2HQFcJOJipVpuh3w= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Stefan Klug , Kieran Bingham Subject: [PATCH v6 3/8] libcamera: camera: Add indirection to Camera signal emissions Date: Thu, 29 Jan 2026 17:28:09 +0900 Message-ID: <20260129082814.1777779-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- No change in v6 No change in v5 No change in v4 Changes in v3: - Add documentation No change in v2 --- include/libcamera/internal/camera.h | 4 +++ src/libcamera/camera.cpp | 47 +++++++++++++++++++++++++++-- src/libcamera/pipeline_handler.cpp | 2 +- src/libcamera/request.cpp | 2 +- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 8a2e9ed5894d..d28cd921a0f9 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -36,6 +36,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_; std::queue waitingRequests_; ControlInfoMap controlInfo_; diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 7c0e93ff483f..a2132e61a7bd 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -611,6 +611,30 @@ Camera::Private::~Private() * \copydoc Camera::Private::pipe() */ +/** + * \fn Camera::Private::emitBufferCompleted + * \copydoc Camera::bufferCompleted + * + * This is one level of indirection so that we can call into the LayerManager + * before actually emitting the Signal. + */ + +/** + * \fn Camera::Private::emitRequestCompleted + * \copydoc Camera::requestCompleted + * + * This is one level of indirection so that we can call into the LayerManager + * before actually emitting the Signal. + */ + +/** + * \fn Camera::Private::emitDisconnected + * \copydoc Camera::disconnected + * + * This is one level of indirection so that we can call into the LayerManager + * before actually emitting the Signal. + */ + /** * \fn Camera::Private::validator() * \brief Retrieve the control validator related to this camera @@ -746,6 +770,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__ */ /** @@ -956,7 +999,7 @@ void Camera::disconnect() LOG(Camera, Debug) << "Disconnecting camera " << id(); _d()->disconnect(); - disconnected.emit(); + _d()->emitDisconnected(); } int Camera::exportFrameBuffers(Stream *stream, @@ -1486,7 +1529,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 5c469e5bad24..c51ce4048558 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -563,7 +563,7 @@ void PipelineHandler::doQueueRequests(Camera *camera) 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 57f1f060d5b4..aa76188d3e42 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -145,7 +145,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 Jan 29 08:28:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26032 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 91192C3226 for ; Thu, 29 Jan 2026 08:28:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4D39361FD2; Thu, 29 Jan 2026 09:28:35 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hL8zJ8uH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4E26561FCC for ; Thu, 29 Jan 2026 09:28:32 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 59AB62808; Thu, 29 Jan 2026 09:27:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675274; bh=17n2kYWZIEI1n9r1tK45MvjBMYqmfbRlYXrXQHtgIy4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hL8zJ8uHSBWjXElQh/OilBj1Uy7/RfzkQPZZJmi/+r4oanJRSyh6C67AjLpVLojPs m1/oajEblXjmIqUnxyBRnC9f+FcsxI+/BekuWZuUC96CUd47dO/m045GsiHMlPx/cW 6Zxgf6dhO8e4uEKaLnUBYdbkziW/raw1b2iT2yto= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 4/8] libcamera: layer_manager: Add Layer handling implementation Date: Thu, 29 Jan 2026 17:28:10 +0900 Message-ID: <20260129082814.1777779-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 translating the AeEnable control, or implementing the Sync algorithm. To achieve this, add: - a LayerManager, which searches for and loads layers from shared object files - a LayerController, which belongs to a Camera and orchestrates selecting layers for the Camera and 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 v6: - make requestCompleted return metadata, so that layers can set metadata (since Request::metadata() now returns a const) Changes in v5: - add documentation to LayerInstance Changes in v4: - add LayerInstance to wrap LayerLoaded and closure to remove the need for a map to store the closures in LayerController - Also add a level of indirection in LayerInstance to ease calling into the Layer functions from LayerController and remove the need to get and pass the closures at the LayerController level - make LayerManager::createController() const - add LIBCAMERA_LAYER_PATH to meson devenv Changes in v3: - change start() signature so that the layers can modify the initial control list - this is done by the LayerManager having it's own copy of the control list that is modified by the layers, and it returns it to the Camera on its hook - expand documentation about the LayerManager - add documentation for all the LayerManager and Layer functions - clear properties_ and controls_ on terminate() - add LayerController which belongs to a Camera, to remove from LayerManager having to manage a map of camera-layer keys to the closures for each layer 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 | 205 +++++++ include/libcamera/internal/meson.build | 1 + include/libcamera/layer.h | 54 ++ include/libcamera/meson.build | 1 + src/layer/meson.build | 14 + src/libcamera/layer.cpp | 179 ++++++ src/libcamera/layer_manager.cpp | 612 +++++++++++++++++++++ src/libcamera/meson.build | 2 + src/meson.build | 1 + 9 files changed, 1069 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.cpp 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..8427ec3eb5d9 --- /dev/null +++ b/include/libcamera/internal/layer_manager.h @@ -0,0 +1,205 @@ +/* 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 +#include +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LayerLoaded) +LOG_DECLARE_CATEGORY(LayerController) +LOG_DECLARE_CATEGORY(LayerManager) + +/* Extend the layer with information specific to load-handling */ +struct LayerLoaded { + LayerLoaded() = default; + + LayerLoaded(const std::string &file); + + LayerLoaded(LayerLoaded &&other) + : info(other.info), vtable(other.vtable), + dlHandle(other.dlHandle), valid(other.valid) + { + other.dlHandle = nullptr; + } + + LayerLoaded &operator=(LayerLoaded &&other) + { + info = other.info; + vtable = other.vtable; + dlHandle = other.dlHandle; + other.dlHandle = nullptr; + valid = other.valid; + return *this; + } + + ~LayerLoaded() + { + if (dlHandle) + dlclose(dlHandle); + } + + LayerInfo *info = nullptr; + LayerInterface *vtable = nullptr; + void *dlHandle = nullptr; + bool valid = false; + +private: + LIBCAMERA_DISABLE_COPY(LayerLoaded) +}; + +#ifndef __DOXYGEN__ +#define _ARG_PARAMS2(type1, type2) type1 arg1, type2 arg2 +#define _ARG_NAMES2(type1, type2) arg1, arg2 + +#define _ARG_PARAMS1(type1) type1 arg1 +#define _ARG_NAMES1(type1) arg1 + +#define _ARG_PARAMS0() +#define _ARG_NAMES0() + +#define _GET_OVERRIDE(_1, _2, _3, NAME, ...) NAME + +#define ARG_PARAMS(...) _GET_OVERRIDE("ignored", __VA_ARGS__ __VA_OPT__(,) \ + _ARG_PARAMS2, _ARG_PARAMS1, _ARG_PARAMS0)(__VA_ARGS__) + +#define ARG_NAMES(...) _GET_OVERRIDE("ignored", __VA_ARGS__ __VA_OPT__(,) \ + _ARG_NAMES2, _ARG_NAMES1, _ARG_NAMES0)(__VA_ARGS__) +#endif /* __DOXYGEN__ */ + +#define LAYER_INSTANCE_CALL(func, ...) \ + void func(ARG_PARAMS(__VA_ARGS__)) \ + { \ + if (layer->vtable->func) \ + layer->vtable->func(closure __VA_OPT__(,) ARG_NAMES(__VA_ARGS__)); \ + } + +struct LayerInstance { + LayerInstance(const std::shared_ptr &l) + : layer(l) + { + } + + void init(const std::string &id) + { + closure = layer->vtable->init(id); + } + + void terminate() + { + layer->vtable->terminate(closure); + } + + LAYER_INSTANCE_CALL(bufferCompleted, Request *, FrameBuffer *) + LAYER_INSTANCE_CALL(disconnected) + LAYER_INSTANCE_CALL(acquire) + LAYER_INSTANCE_CALL(release) + LAYER_INSTANCE_CALL(configure, const CameraConfiguration *) + LAYER_INSTANCE_CALL(createRequest, uint64_t, const Request *) + LAYER_INSTANCE_CALL(queueRequest, Request *) + LAYER_INSTANCE_CALL(start, ControlList &) + LAYER_INSTANCE_CALL(stop) + + ControlList requestCompleted(Request *request) + { + if (!layer->vtable->requestCompleted) + return ControlList(controls::controls); + return layer->vtable->requestCompleted(closure, request); + } + + ControlInfoMap::Map controls(ControlInfoMap &infoMap) + { + if (!layer->vtable->controls) + return ControlInfoMap::Map(); + return layer->vtable->controls(closure, infoMap); + } + + ControlList properties(ControlList &props) + { + if (!layer->vtable->properties) + return ControlList(controls::controls); + return layer->vtable->properties(closure, props); + } + + const std::shared_ptr layer; + void *closure = nullptr; +}; + +class LayerController +{ +public: + LayerController(const Camera *camera, const ControlList &properties, + const ControlInfoMap &controlInfoMap, + const std::map> &layers); + ~LayerController(); + + void bufferCompleted(Request *request, FrameBuffer *buffer); + ControlList requestCompleted(Request *request); + void disconnected(); + + void acquire(); + void release(); + + const ControlInfoMap &controls() const { return controls_; } + const ControlList &properties() const { return properties_; } + + void configure(const CameraConfiguration *config, + const ControlInfoMap &controlInfoMap); + + void createRequest(uint64_t cookie, const Request *request); + + void queueRequest(Request *request); + + ControlList *start(const ControlList *controls); + void stop(); + +private: + void updateProperties(const ControlList &properties); + void updateControls(const ControlInfoMap &controlInfoMap); + + std::deque> executionQueue_; + + ControlInfoMap controls_; + ControlList properties_; + + ControlList startControls_ = ControlList(controls::controls); +}; + +class LayerManager +{ +public: + LayerManager(); + ~LayerManager() = default; + + std::unique_ptr + createController(const Camera *camera, + const ControlList &properties, + const ControlInfoMap &controlInfoMap) const; + +private: + std::map> layers_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index ed145a6dd7cd..ca96754f087e 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -31,6 +31,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..1ec37256ad8d --- /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 &); + void (*terminate)(void *); + + void (*bufferCompleted)(void *, Request *, FrameBuffer *); + ControlList (*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 *, 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..45dded512a13 --- /dev/null +++ b/src/layer/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_includes = [ + libcamera_includes, +] + +layer_install_dir = libcamera_libdir / 'layer' + +config_h.set('LAYER_DIR', + '"' + get_option('prefix') / layer_install_dir + '"') + +layers_env = environment() +layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir()) +meson.add_devenv(layers_env) diff --git a/src/libcamera/layer.cpp b/src/libcamera/layer.cpp new file mode 100644 index 000000000000..64debfb0c9e2 --- /dev/null +++ b/src/libcamera/layer.cpp @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer interface + */ + +#include + +/** + * \file layer.h + * \brief Layer interface + * + * Layers are a construct that lives in between the application and libcamera. + * They are hooked into select calls to and from Camera, and each one is + * executed in order. + * + * \todo Expand on this, and decide on more concrete naming like module vs implementation + */ + +namespace libcamera { + +/** + * \struct LayerInfo + * \brief Information about a Layer implementation + * + * This struct gives information about a layer implementation, such as name and + * API version. It must be exposed, named 'layerInfo', by the layer + * implementation shared object to identify itself to the LayerManager. + */ + +/** + * \var LayerInfo::name + * \brief Name of the Layer module + */ + +/** + * \var LayerInfo::layerAPIVersion + * \brief API version of the Layer implementation + */ + +/** + * \struct LayerInterface + * \brief The function table of the Layer implementation + * + * This struct is the function table of a layer implementation. Any functions + * that the layer implements should be filled in here, and any functions that + * are not implemented must be set to nullptr. This struct, named + * 'layerInterface', must be exposed by the layer implementation shared object. + */ + +/** + * \var LayerInterface::init + * \brief Initialize the layer + * \param[in] name Name of the camera + * + * This function is a required function for layer implementations. + * + * This function is called on Camera construction, and is where the layer + * implementation should allocate and initialize its closure and anything else + * required to run. + * + * \return A closure + */ + +/** + * \var LayerInterface::terminate + * \brief Terminate the layer + * \param[in] closure The closure that was allocated at init() + * + * This function is a required function for layer implementations. + * + * This function is called on Camera deconstruction, and is where the layer + * should free its closure and anything else that was allocated at + * initialization. + */ + +/** + * \var LayerInterface::bufferCompleted + * \brief Hook for Camera::bufferCompleted + * \param[in] closure The closure of the layer + */ + +/** + * \var LayerInterface::requestCompleted + * \brief Hook for Camera::requestCompleted + * \param[in] closure The closure of the layer + * \return Metadata that the layer wants to set + */ + +/** + * \var LayerInterface::disconnected + * \brief Hook for Camera::disconnected + * \param[in] closure The closure of the layer + */ + +/** + * \var LayerInterface::acquire + * \brief Hook for Camera::acquire + * \param[in] closure The closure of the layer + */ + +/** + * \var LayerInterface::release + * \brief Hook for Camera::release + * \param[in] closure The closure of the layer + */ + +/** + * \var LayerInterface::controls + * \brief Declare the controls supported by the Layer + * \param[in] closure The closure of the layer + * \param[in] controlInfoMap The cumulative ControlInfoMap of supported controls of the Camera and any previous layers + * + * This function is for the layer implementation to declare the controls that + * it supports. This will be called by the LayerManager at Camera::init() time + * (after LayerInterface::init()), and at Camera::configure() time. The latter + * gives a chance for the controls to be updated if the configuration changes + * them. + * + * The controls that are returned by this function will overwrite any + * duplicates that were in the input parameter controls. + * + * \return The additional controls that this Layer implements + */ + +/** + * \var LayerInterface::properties + * \brief Declare the properties supported by the Layer + * \param[in] closure The closure of the layer + * \param[in] controlList The cumulative properties of the Camera and any previous layers + * + * This function is for the layer implementation to declare the properies that + * it wants to declare. This will be called by the LayerManager once at + * Camera::init() time (after LayerInterface::init(), and before + * LayerInterface::controls()). + * + * The properties that are returned by this function will overwrite any + * duplicates that were in the input parameter properties. + * + * \return The additional properties that this Layer declares + */ + +/** + * \var LayerInterface::configure + * \brief Hook for Camera::configure + * \param[in] closure The closure of the layer + * \param[in] cameraConfiguration The camera configuration + */ + +/** + * \var LayerInterface::createRequest + * \brief Hook for Camera::createRequest + * \param[in] closure The closure of the layer + * \param[in] cookie An opaque cookie for the application + * \param[in] request The request that was just created by the Camera + */ + +/** + * \var LayerInterface::queueRequest + * \brief Hook for Camera::queueRequest + * \param[in] closure The closure of the layer + * \param[in] request The request that was queued + */ + +/** + * \var LayerInterface::start + * \brief Hook for Camera::start + * \param[in] closure The closure of the layer + * \param[in] controls The controls to be set before starting capture + */ + +/** + * \var LayerInterface::stop + * \brief Hook for Camera::stop + * \param[in] closure The closure of the layer + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/layer_manager.cpp b/src/libcamera/layer_manager.cpp new file mode 100644 index 000000000000..bef69f6bd04b --- /dev/null +++ b/src/libcamera/layer_manager.cpp @@ -0,0 +1,612 @@ +/* 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 "libcamera/internal/utils.h" + +/** + * \file layer_manager.h + * \brief Layer manager + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LayerLoaded) +LOG_DEFINE_CATEGORY(LayerController) +LOG_DEFINE_CATEGORY(LayerManager) + +/** + * \class LayerLoaded + * \brief A wrapper class for a Layer shared object that has been loaded + * + * This class wraps a Layer shared object that has been loaded, managing the + * lifetime and management of the dlopened handle as well as organizing access + * to the function table and layer info. + * + * Instances of this class shall not be used directly. LayerInstance, which + * wraps LayerLoaded, shall be used instead. + * + * \sa LayerInstance + */ + +/** + * \var LayerLoaded::info + * \brief Information about the Layer + */ + +/** + * \var LayerLoaded::vtable + * \brief The function table of the layer + */ + +/** + * \var LayerLoaded::dlHandle + * \brief The handle as returned by dlopen for the layer shared object + */ + +/** + * \var LayerLoaded::valid + * \brief Whether or not the loaded layer is valid + * + * Instances that failed to load due to error, or that were constructed with no + * parameters will be invalid. + */ + +/** + * \brief Load a Layer from a shared object file + */ +LayerLoaded::LayerLoaded(const std::string &filename) +{ + File file{ filename }; + if (!file.open(File::OpenModeFlag::ReadOnly)) { + LOG(LayerLoaded, Error) << "Failed to open layer: " + << strerror(-file.error()); + return; + } + + Span data = file.map(); + int ret = utils::elfVerifyIdent(data); + if (ret) { + LOG(LayerLoaded, Error) << "Layer is not an ELF file"; + return; + } + + Span layerInfoSym = utils::elfLoadSymbol(data, "layerInfo"); + if (layerInfoSym.size() < sizeof(LayerInfo)) { + LOG(LayerLoaded, Error) << "Layer has no valid layerInfoSym"; + return; + } + + void *dlh = dlopen(file.fileName().c_str(), RTLD_LAZY); + if (!dlh) { + LOG(LayerLoaded, Error) + << "Failed to open layer shared object: " + << dlerror(); + return; + } + + /* No need to dlclose as the deconstructor will handle it */ + + void *layerInfoDl = dlsym(dlh, "layerInfo"); + if (!layerInfoDl) { + LOG(LayerLoaded, Error) + << "Failed to load layerInfo from layer shared object: " + << dlerror(); + return; + } + + void *vtableSym = dlsym(dlh, "layerInterface"); + if (!vtableSym) { + LOG(LayerLoaded, Error) + << "Failed to load layerInterface from layer shared object: " + << dlerror(); + return; + } + + info = static_cast(layerInfoDl); + vtable = static_cast(vtableSym); + dlHandle = dlh; + + /* \todo Implement this. It should come from the libcamera version */ + if (info->layerAPIVersion != 1) { + LOG(LayerLoaded, Error) << "Layer '" << info->name + << "' API version mismatch"; + return; + } + + /* \todo Document these requirements */ + if (!vtable->init) { + LOG(LayerLoaded, Error) << "Layer '" << info->name + << "' doesn't implement init"; + return; + } + + /* \todo Document these requirements */ + if (!vtable->terminate) { + LOG(LayerLoaded, Error) << "Layer '" << info->name + << "' doesn't implement terminate"; + return; + } + + /* \todo Validate the layer name. */ + + valid = true; + + return; +} + +/** + * \fn LayerLoaded::LayerLoaded(LayerLoaded &&other) + * \brief Move constructor + */ + +/** + * \fn LayerLoaded &LayerLoaded::operator=(LayerLoaded &&other) + * \brief Move assignment operator + */ + +/** + * \def LAYER_INSTANCE_CALL + * \brief Call a function from the layer's vtable + * \param func The function name to call from the vtable + * + * This is a convenience function to reduce code duplication for checking that + * the function exists in the layer's vtable before calling it. + */ + +/** + * \struct LayerInstance + * \brief A Layer that has been instantiated + * + * This struct represents a Layer that has been instantiated. It wraps an + * instance-agnostic LayerLoaded (which notably contains the layer vtable) with + * instance-specific data (in a closure). + * + * All functions (except for the constructor, controls(), and properties()) are + * simply a redirection into the underlying LayerLoaded's vtable, plus a check + * (if appropriate) that the function exists in the vtable. + */ + +/** + * \fn LayerInstance::LayerInstance(const std::shared_ptr &l) + * \brief Construct a LayerInstance from a LayerLoaded + * \param l LayerLoaded from which to construct a LayerInstance + */ + +/** + * \fn LayerInstance::init(const std::string &id) + * \brief Initialize the layer + * \param id Name of the camera + * + * This function calls init() of the wrapped LayerLoaded, and saves the closure + * that is returned by LayerLoaded::init(). + * + * \sa LayerLoaded::init() + */ + +/** + * \fn LayerInstance::terminate() + * \brief Terminate the layer + * + * This function calls terminate() of the wrapped LayerLoaded. + * + * \sa LayerLoaded::terminate() + */ + +/** + * \fn LayerInstance::requestCompleted(Request *request) + * \brief Handler for when a Request completes + * \param request The request that has completed + * \return The metadata that this Layer reports for the completed \a request + */ + +/** + * \fn LayerInstance::controls(ControlInfoMap &infoMap) + * \brief Declare the controls supported by the Layer + * \param infoMap The cumulative ControlInfoMap of supported controls of the + * Camera and any previous layers + * + * This function calls controls() of the wrapped LayerLoaded, if controls() + * exists in the vtable. If it does not then an empty ControlInfoMap::Map is + * returned. + * + * \sa LayerLoaded::controls() + * + * \return The additional controls that this Layer implements, or an empty + * ControlInfoMap::Map if controls() is not implemented by the Layer + */ + +/** + * \fn LayerInstance::properties(ControlList &props) + * \brief Declare the properties supported by the Layer + * \param props The cumulative properties of the Camera and any previous layers + * + * This function calls properties() of the wrapped LayerLoaded, if properties() + * exists in the vtable. If it does not then an empty ControlList (made from + * controls::controls) is returned. + * + * \sa LayerLoaded::properties() + * + * \return The additional properties that this Layer implements, or an empty + * ControlList if properties() is not implemented by the Layer + */ + +/** + * \var LayerInstance::layer + * \brief The LayerLoaded that this LayerInstance wraps + */ + +/** + * \var LayerInstance::closure + * \brief The closure of this LayerInstance + */ + +/** + * \class LayerController + * \brief Per-Camera instance of a layer manager + * + * Conceptually this class is an instantiation of the LayerManager for each + * Camera instance. It contains the closure for of each layer specific to the + * Camera, as well as the queue of layers to execute for each Camera. + */ + +/** + * \brief Initialize the Layers + * \param[in] camera The Camera for whom to initialize layers + * \param[in] properties The Camera properties + * \param[in] controlInfoMap The Camera controls + * \param[in] layers Map of available layers + * + * This is called by the Camera at construction time via + * LayerManager::createController. The LayerManager feeds the list of layers + * that are available, and the LayerController can then create its own + * execution queue and initialize all the layers for its Camera. + * + * \a properties and \a controlInfoMap are passed in so that the Layers can + * modify them, although they will be cached in an internal copy that can be + * efficiently returned at properties() and controls(), respectively. + */ +LayerController::LayerController(const Camera *camera, + const ControlList &properties, + const ControlInfoMap &controlInfoMap, + const std::map> &layers) +{ + /* Order the layers */ + /* \todo Document this. First is closer to application, last is closer to libcamera */ + /* \todo Get this from configuration file */ + 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()) { + LOG(LayerController, Warning) + << "Requested layer '" << layerName + << "' not found"; + continue; + } + + executionQueue_.emplace_back(std::make_unique(it->second)); + } + } + + for (std::unique_ptr &layer : executionQueue_) + layer->init(camera->id()); + + /* + * 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(properties); + updateControls(controlInfoMap); +} + +/** + * \brief Terminate the Layers + * + * This is called by the Camera at deconstruction time. The LayerController + * instructs all Layer instances to release the resources that they allocated + * for this specific \a camera. + */ +LayerController::~LayerController() +{ + for (std::unique_ptr &layer : executionQueue_) + layer->terminate(); +} + +/** + * \brief Hook for Camera::bufferCompleted + * \param[in] request The request whose buffer completed + * \param[in] buffer The buffer that completed + */ +void LayerController::bufferCompleted(Request *request, FrameBuffer *buffer) +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + (*it)->bufferCompleted(request, buffer); + } +} + +/** + * \brief Hook for Camera::requestCompleted + * \param[in] request The request that completed + * \return metadata set by all the layers + */ +ControlList LayerController::requestCompleted(Request *request) +{ + ControlList ret(controls::controls); + + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + ControlList metadata = (*it)->requestCompleted(request); + ret.merge(metadata, ControlList::MergePolicy::OverwriteExisting); + } + + return ret; +} + +/** + * \brief Hook for Camera::disconnected + */ +void LayerController::disconnected() +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + (*it)->disconnected(); + } +} + +/** + * \brief Hook for Camera::acquire + */ +void LayerController::acquire() +{ + for (std::unique_ptr &layer : executionQueue_) + layer->acquire(); +} + +/** + * \brief Hook for Camera::release + */ +void LayerController::release() +{ + for (std::unique_ptr &layer : executionQueue_) + layer->release(); +} + +/** + * \fn LayerController::controls + * \brief Hook for Camera::controls + * \return A ControlInfoMap that merges the Camera's controls() with the ones + * declared by the layers + */ + +/** + * \fn LayerController::properties + * \brief Hook for Camera::properties + * \return A properties list that merges the Camera's properties() with the + * ones declared by the layers + */ + +void LayerController::updateProperties(const ControlList &properties) +{ + ControlList props = properties; + for (std::unique_ptr &layer : executionQueue_) { + ControlList ret = layer->properties(props); + props.merge(ret, ControlList::MergePolicy::OverwriteExisting); + } + properties_ = props; +} + +void LayerController::updateControls(const ControlInfoMap &controlInfoMap) +{ + ControlInfoMap infoMap = controlInfoMap; + /* \todo Simplify this once ControlInfoMaps become easier to modify */ + for (std::unique_ptr &layer : executionQueue_) { + ControlInfoMap::Map ret = layer->controls(infoMap); + ControlInfoMap::Map map; + /* Merge the layer's ret first since insert doesn't overwrite */ + map.insert(ret.begin(), ret.end()); + map.insert(infoMap.begin(), infoMap.end()); + infoMap = ControlInfoMap(std::move(map), + libcamera::controls::controls); + } + controls_ = infoMap; +} + +/** + * \brief Hook for Camera::configure + * \param[in] config The configuration + * \param[in] controlInfoMap The ControlInfoMap of the controls that \a camera supports + * + * \a controlInfoMap is passed in as this is a potential point where the limits + * of controls could change, so this gives a chance for the Layers to update + * the ControlInfoMap that will be returned by LayerController::controls(). + */ +void LayerController::configure(const CameraConfiguration *config, + const ControlInfoMap &controlInfoMap) +{ + for (std::unique_ptr &layer : executionQueue_) + layer->configure(config); + + updateControls(controlInfoMap); +} + +/** + * \brief Hook for Camera::createRequest + * \param[in] cookie An opaque cookie for the application + * \param[in] request The request that was created + */ +void LayerController::createRequest(uint64_t cookie, const Request *request) +{ + for (std::unique_ptr &layer : executionQueue_) + layer->createRequest(cookie, request); +} + +/** + * \brief Hook for Camera::queueRequest + * \param[in] request The request that is being queued + */ +void LayerController::queueRequest(Request *request) +{ + for (std::unique_ptr &layer : executionQueue_) + layer->queueRequest(request); +} + +/** + * \brief Hook for Camera::start + * \param[in] controls The controls to be applied before starting the capture + * \return A ControlList that merges controls set by the layers and \a controls + */ +ControlList *LayerController::start(const ControlList *controls) +{ + if (controls) { + /* Clear any leftover start controls from a previous run */ + startControls_.clear(); + startControls_.merge(*controls); + } + + for (std::unique_ptr &layer : executionQueue_) + layer->start(startControls_); + + return &startControls_; +} + +/** + * \brief Hook for Camera::stop + */ +void LayerController::stop() +{ + for (std::unique_ptr &layer : executionQueue_) + layer->stop(); +} + +/** + * \class LayerManager + * \brief Layer manager + * + * The Layer manager discovers layer implementations from the filesystem, and + * creates LayerLoaded instances for each one. + * + * The LayerManager itself is instantiated by the CameraManager, and each + * Camera interacts with the LayerManager by passing itself in to + * LayerManager::createController. The LayerManager internally maps each Camera + * to a list of Layer instances that it calls sequentially for each hook. + */ + +/** + * \brief Construct a LayerManager instance + * + * The LayerManager class is meant be instantiated by the CameraManager. + * + * This function simply loads all available layers and stores them. The + * LayerController is responsible for organizing them into queues to be + * executed, and for managing closures for each Camera that they belong to. + */ +LayerManager::LayerManager() +{ + /* This is so that we can capture it in the lambda below */ + std::map> &layers = layers_; + + /* \todo Implement built-in layers */ + + /* This returns the number of "modules" successfully loaded */ + std::function soHandler = + [&layers](const std::string &file) { + std::shared_ptr layer = std::make_shared(file); + if (!layer->valid) + return 0; + + LOG(LayerManager, Debug) << "Loaded layer '" << file << "'"; + + auto [it, inserted] = + layers.try_emplace(std::string(layer->info->name), + std::move(layer)); + if (!inserted) + LOG(LayerManager, Warning) + << "Not adding duplicate layer '" + << layer->info->name << "'"; + + 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::findSharedObjects(dir.c_str(), 1, soHandler); + } + } + + /* + * 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::findSharedObjects(layerBuildPath.c_str(), maxDepth, soHandler); + } + + /* Finally try to load layers from the installed system path. */ + utils::findSharedObjects(LAYER_DIR, 1, soHandler); +} + +/** + * \brief Create a LayerController instance + * \param[in] camera The Camera instance for whom to create a LayerController + * \param[in] properties The Camera properties + * \param[in] controlInfoMap The Camera controls + */ +std::unique_ptr +LayerManager::createController(const Camera *camera, + const ControlList &properties, + const ControlInfoMap &controlInfoMap) const +{ + return std::make_unique(camera, properties, controlInfoMap, layers_); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 186d5c48ccd5..076cfda66060 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -41,6 +41,8 @@ libcamera_internal_sources = files([ 'ipc_pipe.cpp', 'ipc_pipe_unixsocket.cpp', 'ipc_unixsocket.cpp', + 'layer.cpp', + 'layer_manager.cpp', 'mapped_framebuffer.cpp', 'matrix.cpp', 'media_device.cpp', diff --git a/src/meson.build b/src/meson.build index 9b63c8e845d8..ef296382718c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -77,6 +77,7 @@ subdir('libcamera') subdir('android') subdir('ipa') +subdir('layer') subdir('apps') From patchwork Thu Jan 29 08:28:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26033 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 7D804C3226 for ; Thu, 29 Jan 2026 08:28:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 19B7461FE7; Thu, 29 Jan 2026 09:28:39 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="N7OVdDBi"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 02CF061FDB for ; Thu, 29 Jan 2026 09:28:34 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 45CD42818; Thu, 29 Jan 2026 09:27:55 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675276; bh=Y5dUEdVs4XHM52NVYwyQiWrLEBJTA53ojcN3Hb6+R/8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N7OVdDBiGvwodlDyxr1EfEr6k65mEnrpmLvgh6xh/TiAOryW0QxUGIPtwLRa5E6Nd 1gEO5vVSWSe4HKXd7tN/mcXAr7t0sBk6HwbFXwt3xfDztsaR3b5CuLNn7A/etnfjuN 6oFShpbdNw+Joj5JAsr74dZRfCarZ6LY26w6pM1s= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , Kieran Bingham Subject: [PATCH v6 5/8] libcamera: camera_manager: Add LayerManager Date: Thu, 29 Jan 2026 17:28:11 +0900 Message-ID: <20260129082814.1777779-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 through the CameraManager. Signed-off-by: Paul Elder Reviewed-by: Kieran Bingham --- No change in v6 No change in v5 Changes in v4: - Put LayerManager directly in CameraManager, as opposed to a unique pointer No change in v3 New in v2 --- include/libcamera/internal/camera_manager.h | 3 +++ src/libcamera/camera_manager.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h index 755928ce60a5..cc3ede02507f 100644 --- a/include/libcamera/internal/camera_manager.h +++ b/include/libcamera/internal/camera_manager.h @@ -19,6 +19,7 @@ #include #include "libcamera/internal/global_configuration.h" +#include "libcamera/internal/layer_manager.h" #include "libcamera/internal/process.h" namespace libcamera { @@ -45,6 +46,7 @@ public: } IPAManager *ipaManager() const { return ipaManager_.get(); } + const LayerManager *layerManager() const { return &layerManager_; } protected: void run() override; @@ -71,6 +73,7 @@ private: std::unique_ptr enumerator_; std::unique_ptr ipaManager_; + LayerManager layerManager_; const GlobalConfiguration configuration_; }; diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index 23185467ba02..554cd935339a 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -16,6 +16,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/global_configuration.h" +#include "libcamera/internal/layer_manager.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/pipeline_handler.h" From patchwork Thu Jan 29 08:28:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26034 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 D2959C3226 for ; Thu, 29 Jan 2026 08:28:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 827E761FD2; Thu, 29 Jan 2026 09:28:40 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="V8OiMJw5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CB0B961FDE for ; Thu, 29 Jan 2026 09:28:35 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2EF28281C; Thu, 29 Jan 2026 09:27:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675278; bh=jSvnCM+GocAbdguq/5jzEL8b/HNcAV3hEIrhsSHdWss=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=V8OiMJw5ycrd64D+uRVXN/HWfgrTX0IP6WbC85yaYFfork07rnsjefH6cWJ/h8Fya EkAwcLi2QVDVre92Z4x7meU5F3z694E0EQw+VYYBNhiXlFMLKw9L5lrFMy7BfYo6EN cKtO0Gq0zFC7+UAwdE5wqkU2KR3vPkFR51Ic8eKk= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 6/8] libcamera: camera: Hook into the LayerManager Date: Thu, 29 Jan 2026 17:28:12 +0900 Message-ID: <20260129082814.1777779-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 has been chosen based on the capabilities that we want to expose to Layer implementations. Signed-off-by: Paul Elder --- Changes in v6: - merge the metadata returned by the layers in requestCompleted into the request's metadata to forward it to the application No change in v5 No change in v4 Changes in v3: - send the copy of controls returned by LayerManager::start() - each Camera now has a LayerController that it can call into, shortening each hook call significantly 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 --- include/libcamera/internal/camera.h | 4 ++++ src/libcamera/camera.cpp | 32 ++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index d28cd921a0f9..fdabc460a357 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -19,6 +19,8 @@ #include +#include "libcamera/internal/layer_manager.h" + namespace libcamera { class CameraControlValidator; @@ -78,6 +80,8 @@ private: std::atomic state_; std::unique_ptr validator_; + + std::unique_ptr layers_; }; } /* namespace libcamera */ diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index a2132e61a7bd..4ed9a21726d9 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" @@ -774,18 +777,23 @@ void Camera::Private::setState(State state) void Camera::Private::emitBufferCompleted(Request *request, FrameBuffer *buffer) { Camera *camera = _o(); + layers_->bufferCompleted(request, buffer); camera->bufferCompleted.emit(request, buffer); } void Camera::Private::emitRequestCompleted(Request *request) { Camera *camera = _o(); + ControlList metadata = layers_->requestCompleted(request); + request->_d()->metadata().merge(metadata, + ControlList::MergePolicy::OverwriteExisting); camera->requestCompleted.emit(request); } void Camera::Private::emitDisconnected() { Camera *camera = _o(); + layers_->disconnected(); camera->disconnected.emit(); } @@ -976,6 +984,10 @@ Camera::Camera(std::unique_ptr d, const std::string &id, _d()->id_ = id; _d()->streams_ = streams; _d()->validator_ = std::make_unique(this); + _d()->layers_ = + _d()->pipe()->cameraManager()->_d()->layerManager()->createController(this, + _d()->properties_, + _d()->controlInfo_); } Camera::~Camera() @@ -1065,6 +1077,8 @@ int Camera::acquire() return -EBUSY; } + d->layers_->acquire(); + d->setState(Private::CameraAcquired); return 0; @@ -1097,6 +1111,8 @@ int Camera::release() d->pipe_->invokeMethod(&PipelineHandler::release, ConnectionTypeBlocking, this); + d->layers_->release(); + d->setState(Private::CameraAvailable); return 0; @@ -1114,7 +1130,7 @@ int Camera::release() */ const ControlInfoMap &Camera::controls() const { - return _d()->controlInfo_; + return _d()->layers_->controls(); } /** @@ -1127,7 +1143,7 @@ const ControlInfoMap &Camera::controls() const */ const ControlList &Camera::properties() const { - return _d()->properties_; + return _d()->layers_->properties(); } /** @@ -1276,6 +1292,8 @@ int Camera::configure(CameraConfiguration *config) d->activeStreams_.insert(stream); } + d->layers_->configure(config, d->controlInfo_); + d->setState(Private::CameraConfigured); return 0; @@ -1316,6 +1334,8 @@ std::unique_ptr Camera::createRequest(uint64_t cookie) /* Associate the request with the pipeline handler. */ d->pipe_->registerRequest(request.get()); + d->layers_->createRequest(cookie, request.get()); + return request; } @@ -1417,6 +1437,8 @@ int Camera::queueRequest(Request *request) /* Pre-process AeEnable. */ patchControlList(request->controls()); + d->layers_->queueRequest(request); + d->pipe_->invokeMethod(&PipelineHandler::queueRequest, ConnectionTypeQueued, request); @@ -1453,8 +1475,10 @@ int Camera::start(const ControlList *controls) ASSERT(d->requestSequence_ == 0); + ControlList *newControls = d->layers_->start(controls); + if (controls) { - ControlList copy(*controls); + ControlList copy(*newControls); patchControlList(copy); ret = d->pipe_->invokeMethod(&PipelineHandler::start, ConnectionTypeBlocking, this, ©); @@ -1505,6 +1529,8 @@ int Camera::stop() d->setState(Private::CameraStopping); + d->layers_->stop(); + d->pipe_->invokeMethod(&PipelineHandler::stop, ConnectionTypeBlocking, this); From patchwork Thu Jan 29 08:28:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26035 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 08506C3226 for ; Thu, 29 Jan 2026 08:28:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 91DB261FDE; Thu, 29 Jan 2026 09:28:41 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cJlMnBq0"; 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 5DB7761FC6 for ; Thu, 29 Jan 2026 09:28:37 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DC973281A; Thu, 29 Jan 2026 09:27:58 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675279; bh=NHq9VgUc/aNX72WH4DVe26BA4/HGxms7PD1QKg6z9A0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cJlMnBq0MczpsHLQTjLWnvoeEqD2EhB3xgF0QInOBycJK5gdXPSTm05qjn41yDOTC rhdUXhfH5UDFh8XAjV2WfS2rxK/U2ZFwrzibTm7ObakH7bSvIUds3isFekibXfmbf6 WlwMcHCPgtxugLa9toAsTNgELiNo40CcYE5B/MG8= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 7/8] layer: Add layer to inject AeEnable control Date: Thu, 29 Jan 2026 17:28:13 +0900 Message-ID: <20260129082814.1777779-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 v6: - as metadata can no longer be set directly in the Request, instead return it in requestCompleted No change in v5 No change in v4 No change in v3 Changes in v2: - add init() and terminate() - use closures - remove layer namespacing - add gnu_symbol_visibility --- src/layer/inject_controls/inject_controls.cpp | 177 ++++++++++++++++++ src/layer/inject_controls/inject_controls.h | 24 +++ src/layer/inject_controls/meson.build | 16 ++ src/layer/meson.build | 2 + 4 files changed, 219 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..741180f35c60 --- /dev/null +++ b/src/layer/inject_controls/inject_controls.cpp @@ -0,0 +1,177 @@ +/* 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); + } + } +} + +libcamera::ControlList requestCompleted([[maybe_unused]] void *closure, + libcamera::Request *request) +{ + const libcamera::ControlList &metadata = request->metadata(); + libcamera::ControlList ret(libcamera::controls::controls); + + auto eMode = metadata.get(libcamera::controls::ExposureTimeMode); + auto aMode = metadata.get(libcamera::controls::AnalogueGainMode); + + if (!eMode && !aMode) + return ret; + + 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) { + ret.set(libcamera::controls::AeEnable, ag); + return ret; + } + + /* Analogue gain not reported at all; use exposure time only */ + if (!ag && !mg) { + ret.set(libcamera::controls::AeEnable, ae); + return ret; + } + + /* + * Gain mode and exposure mode are not equal; therefore at least one is + * manual, so set AeEnable to false + */ + if (ag != ae) { + ret.set(libcamera::controls::AeEnable, false); + return ret; + } + + /* ag and ae are equal, so just choose one */ + ret.set(libcamera::controls::AeEnable, ag); + return ret; +} + +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..042872d27630 --- /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); +libcamera::ControlList 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 45dded512a13..3d8b70ad2cd2 100644 --- a/src/layer/meson.build +++ b/src/layer/meson.build @@ -12,3 +12,5 @@ config_h.set('LAYER_DIR', layers_env = environment() layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir()) meson.add_devenv(layers_env) + +subdir('inject_controls') From patchwork Thu Jan 29 08:28:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26036 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 C116CC3226 for ; Thu, 29 Jan 2026 08:28:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 768E561FE4; Thu, 29 Jan 2026 09:28:43 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jXyLGT4a"; 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 04EEA61FE2 for ; Thu, 29 Jan 2026 09:28:39 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8C8FF6A6; Thu, 29 Jan 2026 09:28:00 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675281; bh=a2hT0gPuYo7MODV/zEOPDXAv4CrOeNM5yNV2wzIyCg0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jXyLGT4a16Q+hTbVmFMIFEt4fRcD3Rf8K21DUczQ2dYKpkmycMl133jF8Y1iaRF/e JW9vQkx9EXLn7FsJcSYYmWH3OuV0ppW43QIDDbV3BL7HnFBQgfBy49eUtn0kCW4MDF J6u9COhdisjIdJOnbtUxST3YevT/jwdzIEdZOBXA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 8/8] camera, ipa: all: Remove AeEnable handling Date: Thu, 29 Jan 2026 17:28:14 +0900 Message-ID: <20260129082814.1777779-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-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 v6 No change in v5 No change in v4 No change in v3 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 | 42 +------------------- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 5 --- 5 files changed, 3 insertions(+), 49 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 014fd12452ac..2acc45cc0b04 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 1ecaff680978..55cadb1793a5 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[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(controls()); diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index 322694985e72..eceb9d14d74d 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -56,8 +56,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 4ed9a21726d9..29042fce1466 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1339,33 +1339,6 @@ std::unique_ptr Camera::createRequest(uint64_t cookie) return request; } -/** - * \brief Patch a control list that contains the AeEnable control - * \param[inout] controls The control list to be patched - * - * The control list is patched in place, turning the AeEnable control into - * the equivalent ExposureTimeMode/AnalogueGainMode controls. - */ -void Camera::patchControlList(ControlList &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); - } - } -} - /** * \brief Queue a request to the camera * \param[in] request The request to queue to the camera @@ -1434,9 +1407,6 @@ int Camera::queueRequest(Request *request) } } - /* Pre-process AeEnable. */ - patchControlList(request->controls()); - d->layers_->queueRequest(request); d->pipe_->invokeMethod(&PipelineHandler::queueRequest, @@ -1477,16 +1447,8 @@ int Camera::start(const ControlList *controls) ControlList *newControls = d->layers_->start(controls); - if (controls) { - ControlList copy(*newControls); - patchControlList(copy); - ret = d->pipe_->invokeMethod(&PipelineHandler::start, - ConnectionTypeBlocking, this, ©); - } else { - ret = d->pipe_->invokeMethod(&PipelineHandler::start, - ConnectionTypeBlocking, this, nullptr); - } - + ret = d->pipe_->invokeMethod(&PipelineHandler::start, + ConnectionTypeBlocking, this, newControls); if (ret) return ret; diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index 3bd51733d400..bad20d2c7712 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -610,11 +610,6 @@ int UVCCameraData::init(std::shared_ptr 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); /*