From patchwork Mon Jul 28 11:55:48 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23998 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 5C72CC3323 for ; Mon, 28 Jul 2025 11:56:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7410C69164; Mon, 28 Jul 2025 13:56:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vT2Yu+Ga"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 642F069156 for ; Mon, 28 Jul 2025 13:56:08 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 29B844A4; Mon, 28 Jul 2025 13:55:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703726; bh=35DOxD6u7Y5Ga1b7B3r3AlBZNSJtjpQLnX7zEF8xWOg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vT2Yu+Gaky7Yp7ha3IQl+1FWwsl1EPyZjXHKsavXn0Ct2wzcrxYCkX80fDRQmBguU Np2p3MVlfk/LF4jJdykb0lJaEczCEeyyGFWCBNyYOR+ltU3mz1ECEEA4V6/jds1I8/ E2dW9tmRfef1BpjzLHcZsFXazE87v/uTmzq6yjK4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 1/8] libcamera: ipa_manager: Factor out .so file searching Date: Mon, 28 Jul 2025 20:55:48 +0900 Message-ID: <20250728115556.2886082-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-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 --- 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 | 108 ++++------------------- src/libcamera/meson.build | 1 + src/libcamera/utils.cpp | 107 ++++++++++++++++++++++ 6 files changed, 151 insertions(+), 93 deletions(-) create mode 100644 include/libcamera/internal/utils.h create mode 100644 src/libcamera/utils.cpp diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index a0d448cf9862..6b0ba4b5477f 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -59,10 +59,6 @@ public: #endif private: - void parseDir(const char *libDir, unsigned int maxDepth, - std::vector &files); - unsigned int addDir(const char *libDir, unsigned int maxDepth = 0); - IPAModule *module(PipelineHandler *pipe, uint32_t minVersion, uint32_t maxVersion); diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 5c80a28c4cbe..690f5c5ec9f6 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -41,6 +41,7 @@ libcamera_internal_headers = files([ 'shared_mem_object.h', 'source_paths.h', 'sysfs.h', + 'utils.h', 'v4l2_device.h', 'v4l2_pixelformat.h', 'v4l2_subdevice.h', diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h new file mode 100644 index 000000000000..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 830750dcc0fb..1d4de71722df 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -8,9 +8,8 @@ #include "libcamera/internal/ipa_manager.h" #include -#include +#include #include -#include #include #include @@ -19,6 +18,7 @@ #include "libcamera/internal/ipa_module.h" #include "libcamera/internal/ipa_proxy.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/utils.h" /** * \file ipa_manager.h @@ -110,6 +110,20 @@ IPAManager::IPAManager() unsigned int ipaCount = 0; + auto &modules = modules_; + std::function 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 char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH"); if (modulePaths) { @@ -117,7 +131,7 @@ IPAManager::IPAManager() if (dir.empty()) continue; - ipaCount += addDir(dir.c_str()); + ipaCount += utils::findSharedObjects(dir.c_str(), 0, soHandler); } if (!ipaCount) @@ -138,11 +152,11 @@ IPAManager::IPAManager() << "libcamera is not installed. Adding '" << ipaBuildPath << "' to the IPA search path"; - ipaCount += addDir(ipaBuildPath.c_str(), maxDepth); + ipaCount += utils::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) @@ -151,90 +165,6 @@ IPAManager::IPAManager() IPAManager::~IPAManager() = default; -/** - * \brief Identify shared library objects within a directory - * \param[in] libDir The directory to search for shared objects - * \param[in] maxDepth The maximum depth of sub-directories to parse - * \param[out] files A vector of paths to shared object library files - * - * Search a directory for .so files, allowing recursion down to sub-directories - * no further than the depth specified by \a maxDepth. - * - * Discovered shared objects are added to the \a files vector. - */ -void IPAManager::parseDir(const char *libDir, unsigned int maxDepth, - std::vector &files) -{ - struct dirent *ent; - DIR *dir; - - dir = opendir(libDir); - if (!dir) - return; - - while ((ent = readdir(dir)) != nullptr) { - if (ent->d_type == DT_DIR && maxDepth) { - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) - continue; - - std::string subdir = std::string(libDir) + "/" + ent->d_name; - - /* Recursion is limited to maxDepth. */ - parseDir(subdir.c_str(), maxDepth - 1, files); - - continue; - } - - int offset = strlen(ent->d_name) - 3; - if (offset < 0) - continue; - if (strcmp(&ent->d_name[offset], ".so")) - continue; - - files.push_back(std::string(libDir) + "/" + ent->d_name); - } - - closedir(dir); -} - -/** - * \brief Load IPA modules from a directory - * \param[in] libDir The directory to search for IPA modules - * \param[in] maxDepth The maximum depth of sub-directories to search - * - * This function tries to create an IPAModule instance for every shared object - * found in \a libDir, and skips invalid IPA modules. - * - * Sub-directories are searched up to a depth of \a maxDepth. A \a maxDepth - * value of 0 only searches the directory specified in \a libDir. - * - * \return Number of modules loaded by this call - */ -unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) -{ - std::vector files; - - parseDir(libDir, maxDepth, files); - - /* Ensure a stable ordering of modules. */ - std::sort(files.begin(), files.end()); - - unsigned int count = 0; - for (const std::string &file : files) { - auto ipaModule = std::make_unique(file); - if (!ipaModule->isValid()) - continue; - - LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'"; - - modules_.push_back(std::move(ipaModule)); - count++; - } - - return count; -} - /** * \brief Retrieve an IPA module that matches a given pipeline handler * \param[in] pipe The pipeline handler diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index de1eb99b28fd..6a71b2903d27 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -51,6 +51,7 @@ libcamera_internal_sources = files([ 'shared_mem_object.cpp', 'source_paths.cpp', 'sysfs.cpp', + 'utils.cpp', 'v4l2_device.cpp', 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp new file mode 100644 index 000000000000..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 Mon Jul 28 11:55:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 23999 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 3096CBDCC1 for ; Mon, 28 Jul 2025 11:56:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BA1B169169; Mon, 28 Jul 2025 13:56:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="de5cVVMz"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B866369158 for ; Mon, 28 Jul 2025 13:56:10 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3688D379; Mon, 28 Jul 2025 13:55:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703728; bh=15dfkkcgj/FIIo/dCFDM5G8eZKG5udpdZQePCf2FVCM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=de5cVVMzbeL7262FQaakw1v9aLzyt9t9BVndg5xDjLWCQdjNCSG38tP7dQ8AbIWp9 Uq/3L4xpS4I5zLUw8KFixrcDLtN9ErWOx+Zr4Qq3bRQB0M/NBdGOM5/TTFq5Co6dPu bXVV7WEMqbzInL3xp5dV00li+0IgIVCNaogvxLik= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com, Stefan Klug Subject: [PATCH v3 2/8] libcamera: ipa_module: Factor out ELF file handling Date: Mon, 28 Jul 2025 20:55:49 +0900 Message-ID: <20250728115556.2886082-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-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 --- 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 | 163 +++++++++++++++++++++++++++++ 3 files changed, 172 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..2cfc72e03590 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,126 @@ 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 Mon Jul 28 11:55:50 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24000 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 3CF07C3323 for ; Mon, 28 Jul 2025 11:56:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A0A726916F; Mon, 28 Jul 2025 13:56:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mtSqQUlM"; 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 044C26915F for ; Mon, 28 Jul 2025 13:56:13 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C9A16DC; Mon, 28 Jul 2025 13:55:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703731; bh=ch6CzQ4knxf2A1jDaoEblQso/M9vCieLW4Ene6CBpIk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mtSqQUlMY9Y5Fx1RLFoNKOJFNs0rZ0H3+gocD/fIxsrfSznK08pqhKbQoPCioBF0C dkgQSS/LOVMCQHn40C9mUykllw20i0HdH6DKCYAZ/3IHVFNPtdn+t6kbVBxbNTcdJ+ ClpOJ3tn5sV+vBQ6JXgLKkOvBCzSI8maypQioYeE= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com, Stefan Klug Subject: [PATCH v3 3/8] libcamera: camera: Add indirection to Camera signal emissions Date: Mon, 28 Jul 2025 20:55:50 +0900 Message-ID: <20250728115556.2886082-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-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 --- 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 2e1e146a25b1..99aed4f0703a 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, @@ -1480,7 +1523,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 e5f9e55c9783..a7d98fe2fa65 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -552,7 +552,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 7f1e11e8f913..b4ae0f41a34c 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -137,7 +137,7 @@ void Request::Private::doCancelRequest() for (FrameBuffer *buffer : pending_) { buffer->_d()->cancel(); - camera_->bufferCompleted.emit(request, buffer); + camera_->_d()->emitBufferCompleted(request, buffer); } cancelled_ = true; From patchwork Mon Jul 28 11:55:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24001 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 E264AC332A for ; Mon, 28 Jul 2025 11:56:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4CAC369169; Mon, 28 Jul 2025 13:56:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rK73HBUb"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4ACCD69164 for ; Mon, 28 Jul 2025 13:56:15 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C39DE4A4; Mon, 28 Jul 2025 13:55:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703733; bh=dEUi3IZdsRDlj5pRdf3DBM9eVVG1GzM7gOeRjyo/SFI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rK73HBUb8qGnr6nYZsZBIJI1tbDb77YAFegbd9CacwfmE7Yw0Z0qZYWK0y+HZW4Fp aLmW+6t63OpSPZzZC9kjOdt8vAy6T0ujgQK8ALp9v052pm5+JbQCeS9HWZxbEP9Xn5 RxfNbzdcWYuUfrgqyFVl8lZTfdIVfu24FZo101wQ= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 4/8] libcamera: layer_manager: Add Layer handling implementation Date: Mon, 28 Jul 2025 20:55:51 +0900 Message-ID: <20250728115556.2886082-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" We want to be able to implement layers in libcamera, which conceptually sit in between the Camera class and the application. This can be useful for implementing things that don't belong inside the Camera/IPA nor inside the application, such as intercepting and translation the AeEnable control, or implementing the Sync algorithm. To achieve this, 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 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 | 128 +++++ include/libcamera/internal/meson.build | 1 + include/libcamera/layer.h | 54 ++ include/libcamera/meson.build | 1 + src/layer/meson.build | 10 + src/libcamera/layer.cpp | 178 +++++++ src/libcamera/layer_manager.cpp | 551 +++++++++++++++++++++ src/libcamera/meson.build | 2 + src/meson.build | 1 + 9 files changed, 926 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..c8112645c3de --- /dev/null +++ b/include/libcamera/internal/layer_manager.h @@ -0,0 +1,128 @@ +/* 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 + +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) +}; + +class LayerController +{ +public: + LayerController(const Camera *camera, const ControlList &properties, + const ControlInfoMap &controlInfoMap, + std::map> &layers); + ~LayerController(); + + void bufferCompleted(Request *request, FrameBuffer *buffer); + void 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_; + std::map closures_; + + ControlInfoMap controls_; + ControlList properties_; + + std::unique_ptr startControls_; +}; + +class LayerManager +{ +public: + LayerManager(); + ~LayerManager() = default; + + std::unique_ptr + createController(const Camera *camera, + const ControlList &properties, + const ControlInfoMap &controlInfoMap); + +private: + std::map> layers_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 690f5c5ec9f6..20e6c295601f 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -29,6 +29,7 @@ libcamera_internal_headers = files([ 'ipa_proxy.h', 'ipc_pipe.h', 'ipc_unixsocket.h', + 'layer_manager.h', 'mapped_framebuffer.h', 'matrix.h', 'media_device.h', diff --git a/include/libcamera/layer.h b/include/libcamera/layer.h new file mode 100644 index 000000000000..4ab5024a7de2 --- /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 *); + void (*requestCompleted)(void *, Request *); + void (*disconnected)(void *); + + void (*acquire)(void *); + void (*release)(void *); + + ControlInfoMap::Map (*controls)(void *, ControlInfoMap &); + ControlList (*properties)(void *, ControlList &); + + void (*configure)(void *, const CameraConfiguration *); + + void (*createRequest)(void *, uint64_t, const Request *); + + void (*queueRequest)(void *, Request *); + + void (*start)(void *, ControlList &); + void (*stop)(void *); +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index 30ea76f9470a..552af112abb5 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -11,6 +11,7 @@ libcamera_public_headers = files([ 'framebuffer.h', 'framebuffer_allocator.h', 'geometry.h', + 'layer.h', 'logging.h', 'orientation.h', 'pixel_format.h', diff --git a/src/layer/meson.build b/src/layer/meson.build new file mode 100644 index 000000000000..dee5e5ac5804 --- /dev/null +++ b/src/layer/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_includes = [ + libcamera_includes, +] + +layer_install_dir = libcamera_libdir / 'layers' + +config_h.set('LAYER_DIR', + '"' + get_option('prefix') / layer_install_dir + '"') diff --git a/src/libcamera/layer.cpp b/src/libcamera/layer.cpp new file mode 100644 index 000000000000..88372096a136 --- /dev/null +++ b/src/libcamera/layer.cpp @@ -0,0 +1,178 @@ +/* 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 allocated 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 + */ + +/** + * \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..f5239753f40e --- /dev/null +++ b/src/libcamera/layer_manager.cpp @@ -0,0 +1,551 @@ +/* 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. + */ + +/** + * \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 + */ + +/** + * \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, + 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_.push_back(it->second); + } + } + + for (std::shared_ptr &layer : executionQueue_) { + void *closure = layer->vtable->init(camera->id()); + closures_[layer.get()] = closure; + } + + /* + * We need to iterate over the layers individually to merge all of + * their controls, so we'll factor out updateControls() as it needs to be + * run again at configure(). + */ + updateProperties(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::shared_ptr &layer : executionQueue_) { + void *closure = closures_.at(layer.get()); + layer->vtable->terminate(closure); + } +} + +/** + * \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++) { + if ((*it)->vtable->bufferCompleted) { + void *closure = closures_.at((*it).get()); + (*it)->vtable->bufferCompleted(closure, request, buffer); + } + } +} + +/** + * \brief Hook for Camera::requestCompleted + * \param[in] request The request that completed + */ +void LayerController::requestCompleted(Request *request) +{ + /* Reverse order because this comes from a Signal emission */ + for (auto it = executionQueue_.rbegin(); + it != executionQueue_.rend(); it++) { + if ((*it)->vtable->requestCompleted) { + void *closure = closures_.at((*it).get()); + (*it)->vtable->requestCompleted(closure, request); + } + } +} + +/** + * \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++) { + if ((*it)->vtable->disconnected) { + void *closure = closures_.at((*it).get()); + (*it)->vtable->disconnected(closure); + } + } +} + +/** + * \brief Hook for Camera::acquire + */ +void LayerController::acquire() +{ + for (std::shared_ptr &layer : executionQueue_) { + if (layer->vtable->acquire) { + void *closure = closures_.at(layer.get()); + layer->vtable->acquire(closure); + } + } +} + +/** + * \brief Hook for Camera::release + */ +void LayerController::release() +{ + for (std::shared_ptr &layer : executionQueue_) { + if (layer->vtable->release) { + void *closure = closures_.at(layer.get()); + layer->vtable->release(closure); + } + } +} + +/** + * \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::shared_ptr &layer : executionQueue_) { + if (layer->vtable->properties) { + void *closure = closures_.at(layer.get()); + ControlList ret = layer->vtable->properties(closure, 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::shared_ptr &layer : executionQueue_) { + if (layer->vtable->controls) { + void *closure = closures_.at(layer.get()); + ControlInfoMap::Map ret = layer->vtable->controls(closure, infoMap); + ControlInfoMap::Map map; + /* Merge the layer's ret later so that layers can overwrite */ + for (auto &pair : infoMap) + map.insert(pair); + for (auto &pair : ret) + map.insert(pair); + infoMap = ControlInfoMap(std::move(map), + libcamera::controls::controls); + } + } + controls_ = 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::shared_ptr &layer : executionQueue_) { + if (layer->vtable->configure) { + void *closure = closures_.at(layer.get()); + layer->vtable->configure(closure, 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::shared_ptr &layer : executionQueue_) { + if (layer->vtable->createRequest) { + void *closure = closures_.at(layer.get()); + layer->vtable->createRequest(closure, cookie, request); + } + } +} + +/** + * \brief Hook for Camera::queueRequest + * \param[in] request The request that is being queued + */ +void LayerController::queueRequest(Request *request) +{ + for (std::shared_ptr &layer : executionQueue_) { + if (layer->vtable->queueRequest) { + void *closure = closures_.at(layer.get()); + layer->vtable->queueRequest(closure, 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) { + startControls_ = std::make_unique(*controls->idMap()); + startControls_->merge(*controls); + } else { + startControls_ = std::make_unique(controls::controls); + } + + for (std::shared_ptr &layer : executionQueue_) { + if (layer->vtable->start) { + void *closure = closures_.at(layer.get()); + layer->vtable->start(closure, *startControls_); + } + } + + return startControls_.get(); +} + +/** + * \brief Hook for Camera::stop + */ +void LayerController::stop() +{ + for (std::shared_ptr &layer : executionQueue_) { + if (layer->vtable->stop) { + void *closure = closures_.at(layer.get()); + layer->vtable->stop(closure); + } + } +} + +/** + * \class LayerManager + * \brief Layer manager + * + * The Layer manager discovers layer implementations from disk, and creates + * execution queues for every function that is implemented by each layer and + * executes them. A layer is a layer that sits between libcamera and the + * application, and hooks into the public Camera interface. + * + * The LayerManager itself is instantiated by the CameraManager, and each + * Camera interacts with the LayerManager by passing itself it. 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 = + [this, &layers](const std::string &file) { + std::shared_ptr layer = std::make_unique(file); + if (!layer->valid) + return 0; + + LOG(LayerManager, Debug) << "Loaded layer '" << file << "'"; + + layers.emplace(std::string(layer->info->name), layer); + + return 1; + }; + + /* User-specified paths take precedence. */ + /* \todo Document this */ + const char *layerPaths = utils::secure_getenv("LIBCAMERA_LAYER_PATH"); + if (layerPaths) { + for (const auto &dir : utils::split(layerPaths, ":")) { + if (dir.empty()) + continue; + + /* + * \todo Move the shared objects into one directory + * instead of each in their own subdir + */ + utils::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) +{ + return std::make_unique(camera, properties, controlInfoMap, layers_); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 6a71b2903d27..d5b8f8dc4572 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -40,6 +40,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 8eb8f05b362f..37368b01cbf2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -63,6 +63,7 @@ subdir('libcamera') subdir('android') subdir('ipa') +subdir('layer') subdir('apps') From patchwork Mon Jul 28 11:55:52 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24002 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 18FEABDCC1 for ; Mon, 28 Jul 2025 11:56:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C1D4D69169; Mon, 28 Jul 2025 13:56:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nsEDtY+9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BFF1D6917D for ; Mon, 28 Jul 2025 13:56:17 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0FA9F379; Mon, 28 Jul 2025 13:55:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703735; bh=KbTGBggUum9YYxgSW12acluXGM13YWoIy44mw9qHWns=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nsEDtY+9x+eWwMpL6LMgC2LWkXwamVjoJnbJq2cq0YO2+cqFgsg6xzJC7YiEkoIsc wklEM8LOInUmrWo1wvoJgshYn166KlAYsa/gZ0lxOLeVjnoKj+X9hv94nYw7ZAjQOF xAmbCv0n5HRebei0UyE5apdsawWXRuBNc26RktKo= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 5/8] libcamera: camera_manager: Add LayerManager Date: Mon, 28 Jul 2025 20:55:52 +0900 Message-ID: <20250728115556.2886082-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add to the CameraManager a LayerManager member. This allows us to have one LayerManager that handles loading all the layer shared objects, and then each Camera can access the layer via the LayerManager via through the CameraManager. Signed-off-by: Paul Elder Reviewed-by: Kieran Bingham --- New in v2 --- include/libcamera/internal/camera_manager.h | 3 +++ src/libcamera/camera_manager.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h index 0150ca61f7de..ef5b0688de23 100644 --- a/include/libcamera/internal/camera_manager.h +++ b/include/libcamera/internal/camera_manager.h @@ -25,6 +25,7 @@ namespace libcamera { class Camera; class DeviceEnumerator; class IPAManager; +class LayerManager; class PipelineHandlerFactoryBase; class CameraManager::Private : public Extensible::Private, public Thread @@ -39,6 +40,7 @@ public: void removeCamera(std::shared_ptr camera) LIBCAMERA_TSA_EXCLUDES(mutex_); IPAManager *ipaManager() const { return ipaManager_.get(); } + LayerManager *layerManager() const { return layerManager_.get(); } protected: void run() override; @@ -66,6 +68,7 @@ private: std::unique_ptr ipaManager_; ProcessManager processManager_; + std::unique_ptr layerManager_; }; } /* namespace libcamera */ diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index f81794bfd6fe..35c8ed036502 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -15,6 +15,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/layer_manager.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/pipeline_handler.h" @@ -41,6 +42,7 @@ CameraManager::Private::Private() : initialized_(false) { ipaManager_ = std::make_unique(); + layerManager_ = std::make_unique(); } int CameraManager::Private::start() From patchwork Mon Jul 28 11:55:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24003 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 0CE0EC3323 for ; Mon, 28 Jul 2025 11:56:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8482769181; Mon, 28 Jul 2025 13:56:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="c7j4q5VN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5DD8D6916B for ; Mon, 28 Jul 2025 13:56:19 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 171716DC; Mon, 28 Jul 2025 13:55:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703737; bh=W4CwzKBDSYQp/Kc+V5aoL8GkbwqY/RTbwOrCxp6yn0w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=c7j4q5VN0YDmEDy2JI5HQfPweMUsMqC0bz+OJCgvwWkIlTSvjIDKc4O2sob6in8XQ ui1FDNaBd4Yg9fhVoGvii/6fwVv8ccKgKmFh3oDOX3wiHSE6uS8PhjS6WYMOifBcCD fCVqW6U2OouY8Kk1DJ+NQJu/VavkCkvh0tkIIvCw= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 6/8] libcamera: camera: Hook into the LayerManager Date: Mon, 28 Jul 2025 20:55:53 +0900 Message-ID: <20250728115556.2886082-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add hooks into the CameraManager to call into the LayerManager on all relevant public-facing interface of Camera. The entry point for each function into the LayerManager is has been chosen based on the capabilities that we want to allow to Layer implementations. Signed-off-by: Paul Elder --- Changes in 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 | 30 ++++++++++++++++++++++++++--- 2 files changed, 31 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 99aed4f0703a..241e646dcd31 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,21 @@ 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(); + layers_->requestCompleted(request); camera->requestCompleted.emit(request); } void Camera::Private::emitDisconnected() { Camera *camera = _o(); + layers_->disconnected(); camera->disconnected.emit(); } @@ -976,6 +982,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 +1075,8 @@ int Camera::acquire() return -EBUSY; } + d->layers_->acquire(); + d->setState(Private::CameraAcquired); return 0; @@ -1097,6 +1109,8 @@ int Camera::release() d->pipe_->invokeMethod(&PipelineHandler::release, ConnectionTypeBlocking, this); + d->layers_->release(); + d->setState(Private::CameraAvailable); return 0; @@ -1114,7 +1128,7 @@ int Camera::release() */ const ControlInfoMap &Camera::controls() const { - return _d()->controlInfo_; + return _d()->layers_->controls(); } /** @@ -1127,7 +1141,7 @@ const ControlInfoMap &Camera::controls() const */ const ControlList &Camera::properties() const { - return _d()->properties_; + return _d()->layers_->properties(); } /** @@ -1276,6 +1290,8 @@ int Camera::configure(CameraConfiguration *config) d->activeStreams_.insert(stream); } + d->layers_->configure(config, d->controlInfo_); + d->setState(Private::CameraConfigured); return 0; @@ -1316,6 +1332,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; } @@ -1411,6 +1429,8 @@ int Camera::queueRequest(Request *request) /* Pre-process AeEnable. */ patchControlList(request->controls()); + d->layers_->queueRequest(request); + d->pipe_->invokeMethod(&PipelineHandler::queueRequest, ConnectionTypeQueued, request); @@ -1447,8 +1467,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, ©); @@ -1499,6 +1521,8 @@ int Camera::stop() d->setState(Private::CameraStopping); + d->layers_->stop(); + d->pipe_->invokeMethod(&PipelineHandler::stop, ConnectionTypeBlocking, this); From patchwork Mon Jul 28 11:55:54 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24004 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 7E316C332A for ; Mon, 28 Jul 2025 11:56:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1748169189; Mon, 28 Jul 2025 13:56:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hi9WvS/o"; 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 572B16917C for ; Mon, 28 Jul 2025 13:56:21 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1D043379; Mon, 28 Jul 2025 13:55:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703739; bh=pWJSjtrAFC6QKkA3NWNaXZsgb0xLZHDvLnzNoU29evY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hi9WvS/oTeQOSOlZQ/WWiTzvqqZ2Pb/LTaO/4NUYRd8ssHAw9fsRStKr1XcF9vG57 E9xgR+P9Dthxh3GkSj9EtaAA2Zy6WAUJ/5FjvEwmZ38exy6k5hLZ9utO+xh7a/+Zs+ YFmBENfMYih4RT3uAzVg1EarpXYZGc4RordHAryA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 7/8] layer: Add layer to inject AeEnable control Date: Mon, 28 Jul 2025 20:55:54 +0900 Message-ID: <20250728115556.2886082-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a layer to implement the AeEnable control, so that we can remove it from all IPAs and the Camera class, so that it is transparent to all parties and it is available for applications to use. Signed-off-by: Paul Elder --- For layers like this that we forsee to be "always built-in" we might want a way to build it into libcamera instead of into separate shared object files. In any case, this also serves as a demo of how I envision a layer implementation to work. Changes in v2: - add init() and terminate() - use closures - remove layer namespacing - add gnu_symbol_visibility --- src/layer/inject_controls/inject_controls.cpp | 176 ++++++++++++++++++ src/layer/inject_controls/inject_controls.h | 24 +++ src/layer/inject_controls/meson.build | 16 ++ src/layer/meson.build | 2 + 4 files changed, 218 insertions(+) create mode 100644 src/layer/inject_controls/inject_controls.cpp create mode 100644 src/layer/inject_controls/inject_controls.h create mode 100644 src/layer/inject_controls/meson.build diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp new file mode 100644 index 000000000000..f12faff8318c --- /dev/null +++ b/src/layer/inject_controls/inject_controls.cpp @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#include "inject_controls.h" + +#include +#include + +#include +#include +#include +#include + +void *init([[maybe_unused]] const std::string &id) +{ + InjectControls *closure = new InjectControls; + *closure = {}; + return static_cast(closure); +} + +void terminate(void *closure) +{ + InjectControls *obj = static_cast(closure); + delete obj; +} + +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls) +{ + InjectControls *obj = static_cast(closure); + + auto it = ctrls.find(&libcamera::controls::ExposureTimeMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto)) + obj->aeAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual)) + obj->meAvailable = true; + } + } + + it = ctrls.find(&libcamera::controls::AnalogueGainMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto)) + obj->agAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual)) + obj->mgAvailable = true; + } + } + + std::set values; + if (obj->aeAvailable || obj->agAvailable) + values.insert(true); + if (obj->meAvailable || obj->mgAvailable) + values.insert(false); + + if (values.empty()) + return {}; + + if (values.size() == 1) { + bool value = *values.begin(); + return { { &libcamera::controls::AeEnable, + libcamera::ControlInfo(value, value, value) } }; + } + + return { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } }; +} + +void queueRequest(void *closure, libcamera::Request *request) +{ + InjectControls *obj = static_cast(closure); + + libcamera::ControlList &ctrls = request->controls(); + auto aeEnable = ctrls.get(libcamera::controls::AeEnable); + if (!aeEnable) + return; + + if (*aeEnable) { + if (obj->aeAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeAuto); + } + + if (obj->agAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeAuto); + } + } else { + if (obj->meAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeManual); + } + + if (obj->mgAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeManual); + } + } +} + +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request) +{ + libcamera::ControlList &metadata = request->metadata(); + + auto eMode = metadata.get(libcamera::controls::ExposureTimeMode); + auto aMode = metadata.get(libcamera::controls::AnalogueGainMode); + + if (!eMode && !aMode) + return; + + bool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto; + bool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual; + bool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto; + bool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual; + + /* Exposure time not reported at all; use gain only */ + if (!ae && !me) { + metadata.set(libcamera::controls::AeEnable, ag); + return; + } + + /* Analogue gain not reported at all; use exposure time only */ + if (!ag && !mg) { + metadata.set(libcamera::controls::AeEnable, ae); + return; + } + + /* + * Gain mode and exposure mode are not equal; therefore at least one is + * manual, so set AeEnable to false + */ + if (ag != ae) { + metadata.set(libcamera::controls::AeEnable, false); + return; + } + + /* ag and ae are equal, so just choose one */ + metadata.set(libcamera::controls::AeEnable, ag); + return; +} + +namespace libcamera { + +extern "C" { + +[[gnu::visibility("default")]] +struct LayerInfo layerInfo{ + .name = "inject_controls", + .layerAPIVersion = 1, +}; + +[[gnu::visibility("default")]] +struct LayerInterface layerInterface{ + .init = init, + .terminate = terminate, + .bufferCompleted = nullptr, + .requestCompleted = requestCompleted, + .disconnected = nullptr, + .acquire = nullptr, + .release = nullptr, + .controls = updateControls, + .properties = nullptr, + .configure = nullptr, + .createRequest = nullptr, + .queueRequest = queueRequest, + .start = nullptr, + .stop = nullptr, +}; + +} + +} /* namespace libcamera */ diff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h new file mode 100644 index 000000000000..42c094d7f76a --- /dev/null +++ b/src/layer/inject_controls/inject_controls.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#pragma once + +#include +#include + +struct InjectControls { + bool aeAvailable; + bool meAvailable; + bool agAvailable; + bool mgAvailable; +}; + +void *init(const std::string &id); +void terminate(void *closure); +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls); +void queueRequest(void *closure, libcamera::Request *request); +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request); diff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build new file mode 100644 index 000000000000..d1e402746990 --- /dev/null +++ b/src/layer/inject_controls/meson.build @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_name = 'layer_inject_controls' + +layer_inject_sources = files([ + 'inject_controls.h', + 'inject_controls.cpp', +]) + +mod = shared_module(layer_name, layer_inject_sources, + name_prefix : '', + include_directories : [layer_includes], + dependencies : libcamera_public, + gnu_symbol_visibility: 'hidden', + install : true, + install_dir : layer_install_dir) diff --git a/src/layer/meson.build b/src/layer/meson.build index dee5e5ac5804..d5793f8c4525 100644 --- a/src/layer/meson.build +++ b/src/layer/meson.build @@ -8,3 +8,5 @@ layer_install_dir = libcamera_libdir / 'layers' config_h.set('LAYER_DIR', '"' + get_option('prefix') / layer_install_dir + '"') + +subdir('inject_controls') From patchwork Mon Jul 28 11:55:55 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 24005 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 CA1DCC332B for ; Mon, 28 Jul 2025 11:56:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 429786919D; Mon, 28 Jul 2025 13:56:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RmtlcFlf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 64FBC69189 for ; Mon, 28 Jul 2025 13:56:23 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:5715:34ad:7742:5049]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 234C04A4; Mon, 28 Jul 2025 13:55:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753703741; bh=IBbcv3qtPR/Ng2A96f0QLR51Ou+6qLm1jb2uWnMJObc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RmtlcFlftP8EVFsS6+560rRFh3cufAAtHY1KhdGA8WGG2Ylto5Eru+PNMQZpfaVUj OVGsV8+i/BV6ZNLPg9kyZK9h2S992aZnsLp2j5Dk2r6Bmc1VxULE9fo1Aubpnl+1ir i0mHKvgKYi2EODHayQCf71KNgA5nx4538/7vkKnM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder , kieran.bingham@ideasonboard.com, barnabas.pocze@ideasonboard.com Subject: [PATCH v3 8/8] camera, ipa: all: Remove AeEnable handling Date: Mon, 28 Jul 2025 20:55:55 +0900 Message-ID: <20250728115556.2886082-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250728115556.2886082-1-paul.elder@ideasonboard.com> References: <20250728115556.2886082-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Remove AeEnable handling from the Camera and all IPAs, as it is now handled by the inject_controls layer. Signed-off-by: Paul Elder --- The mali-c55 IPA needs to support AnalogueGainMode and ExposureTimeMode, but that is out of scope for this series. No change in v2 --- src/ipa/mali-c55/algorithms/agc.cpp | 1 + src/ipa/rkisp1/algorithms/agc.cpp | 2 - src/ipa/rpi/common/ipa_base.cpp | 2 - src/libcamera/camera.cpp | 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 15963994b2d6..49151415d355 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 35440b67e999..bb1558df5422 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 ce2343e91cde..ace22b0f4187 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -55,8 +55,6 @@ constexpr Duration controllerMinFrameDuration = 1.0s / 30.0; /* List of controls handled by the Raspberry Pi IPA */ const ControlInfoMap::Map ipaControls{ - /* \todo Move this to the Camera class */ - { &controls::AeEnable, ControlInfo(false, true, true) }, { &controls::ExposureTimeMode, ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), ControlValue(controls::ExposureTimeModeManual) } }, diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 241e646dcd31..4d63da020dd6 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1337,33 +1337,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 @@ -1426,9 +1399,6 @@ int Camera::queueRequest(Request *request) } } - /* Pre-process AeEnable. */ - patchControlList(request->controls()); - d->layers_->queueRequest(request); d->pipe_->invokeMethod(&PipelineHandler::queueRequest, @@ -1469,16 +1439,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 4b5816dfdde0..ab12d5b5ae9a 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -607,11 +607,6 @@ int UVCCameraData::init(MediaDevice *media) addControl(cid, info, &ctrls); } - if (autoExposureMode_ && manualExposureMode_) { - /* \todo Move this to the Camera class */ - ctrls[&controls::AeEnable] = ControlInfo(false, true, true); - } - controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); /*