[v11,05/12] config: Look up IPA configurables in configuration file
diff mbox series

Message ID 20250624083612.27230-6-mzamazal@redhat.com
State New
Headers show
Series
  • Add global configuration file
Related show

Commit Message

Milan Zamazal June 24, 2025, 8:36 a.m. UTC
This patch adds configuration options for environment variables used in
the IPA proxy.  Two utility functions configuration retrieval functions
are added, to retrieve lists of values.

The configuration snippet:

  configuration:
    ipa:
      config_paths:
      - config path 1
      - config path 2
      - ...
      module_paths:
      - module path 1
      - module path 2
      - ...
      proxy_paths:
      - proxy path 1
      - proxy path 2
      - ...
      force_isolation: BOOL
      ipas:
        IPA_NAME:
          tuning_file: TUNING FILE

Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../libcamera/internal/global_configuration.h | 35 ++++++++-
 include/libcamera/internal/ipa_manager.h      |  8 ++-
 include/libcamera/internal/ipa_proxy.h        |  5 +-
 src/libcamera/camera_manager.cpp              |  2 +-
 src/libcamera/global_configuration.cpp        | 47 +++++++++++-
 src/libcamera/ipa_manager.cpp                 | 37 ++++++----
 src/libcamera/ipa_proxy.cpp                   | 72 ++++++++++---------
 src/libcamera/pipeline/ipu3/ipu3.cpp          |  3 +-
 src/libcamera/pipeline/mali-c55/mali-c55.cpp  |  2 +
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  4 +-
 .../pipeline/rpi/common/pipeline_base.cpp     |  3 +-
 src/libcamera/pipeline/vimc/vimc.cpp          |  3 +-
 src/libcamera/software_isp/software_isp.cpp   |  4 +-
 test/ipa/ipa_interface_test.cpp               |  3 +-
 .../module_ipa_proxy.cpp.tmpl                 |  4 +-
 .../module_ipa_proxy.h.tmpl                   |  2 +-
 16 files changed, 169 insertions(+), 65 deletions(-)

Comments

Barnabás Pőcze June 27, 2025, 2 p.m. UTC | #1
Hi

2025. 06. 24. 10:36 keltezéssel, Milan Zamazal írta:
> This patch adds configuration options for environment variables used in
> the IPA proxy.  Two utility functions configuration retrieval functions
> are added, to retrieve lists of values.
> 
> The configuration snippet:
> 
>    configuration:
>      ipa:
>        config_paths:
>        - config path 1
>        - config path 2
>        - ...
>        module_paths:
>        - module path 1
>        - module path 2
>        - ...
>        proxy_paths:
>        - proxy path 1
>        - proxy path 2
>        - ...
>        force_isolation: BOOL
>        ipas:
>          IPA_NAME:
>            tuning_file: TUNING FILE
> 
> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>   .../libcamera/internal/global_configuration.h | 35 ++++++++-
>   include/libcamera/internal/ipa_manager.h      |  8 ++-
>   include/libcamera/internal/ipa_proxy.h        |  5 +-
>   src/libcamera/camera_manager.cpp              |  2 +-
>   src/libcamera/global_configuration.cpp        | 47 +++++++++++-
>   src/libcamera/ipa_manager.cpp                 | 37 ++++++----
>   src/libcamera/ipa_proxy.cpp                   | 72 ++++++++++---------
>   src/libcamera/pipeline/ipu3/ipu3.cpp          |  3 +-
>   src/libcamera/pipeline/mali-c55/mali-c55.cpp  |  2 +
>   src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  4 +-
>   .../pipeline/rpi/common/pipeline_base.cpp     |  3 +-
>   src/libcamera/pipeline/vimc/vimc.cpp          |  3 +-
>   src/libcamera/software_isp/software_isp.cpp   |  4 +-
>   test/ipa/ipa_interface_test.cpp               |  3 +-
>   .../module_ipa_proxy.cpp.tmpl                 |  4 +-
>   .../module_ipa_proxy.h.tmpl                   |  2 +-
>   16 files changed, 169 insertions(+), 65 deletions(-)
> 
> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
> index 357119628..cc436703e 100644
> --- a/include/libcamera/internal/global_configuration.h
> +++ b/include/libcamera/internal/global_configuration.h
> @@ -11,6 +11,8 @@
>   #include <optional>
>   #include <string>
>   
> +#include <libcamera/base/utils.h>
> +
>   #include "libcamera/internal/yaml_parser.h"
>   
>   namespace libcamera {
> @@ -24,9 +26,40 @@ public:
>   
>   	unsigned int version() const;
>   	Configuration configuration() const;
> -	std::optional<std::string> option(const std::string &confPath) const;
> +
> +#ifndef __DOXYGEN__
> +	template<typename T,
> +		 std::enable_if_t<
> +			 std::is_same_v<bool, T> ||
> +			 std::is_same_v<double, T> ||
> +			 std::is_same_v<int8_t, T> ||
> +			 std::is_same_v<uint8_t, T> ||
> +			 std::is_same_v<int16_t, T> ||
> +			 std::is_same_v<uint16_t, T> ||
> +			 std::is_same_v<int32_t, T> ||
> +			 std::is_same_v<uint32_t, T> ||
> +			 std::is_same_v<std::string, T> ||
> +			 std::is_same_v<Size, T>> * = nullptr>

Is this needed? `YamlObject::get()` does not have any constraints,
so I think it can be omitted here as well?


> +#else
> +	template<typename T>
> +#endif
> +	std::optional<T> option(const std::string &confPath) const
> +	{
> +		const YamlObject *c = &configuration();
> +		for (auto part : utils::split(confPath, "/")) {
> +			c = &(*c)[part];
> +			if (!*c)
> +				return {};
> +		}
> +		return c->get<T>();
> +	}
> +
> +	std::vector<std::string> listOption(const std::string &confPath) const;
>   	std::optional<std::string> envOption(const char *const envVariable,
>   					     const std::string &confPath) const;
> +	std::vector<std::string> envListOption(
> +		const char *const envVariable,
> +		const std::string &confPath) const;
>   
>   private:
>   	bool loadFile(const std::filesystem::path &fileName);
> diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
> index a0d448cf9..8ad717919 100644
> --- a/include/libcamera/internal/ipa_manager.h
> +++ b/include/libcamera/internal/ipa_manager.h
> @@ -17,6 +17,7 @@
>   #include <libcamera/ipa/ipa_module_info.h>
>   
>   #include "libcamera/internal/camera_manager.h"
> +#include "libcamera/internal/global_configuration.h"
>   #include "libcamera/internal/ipa_module.h"
>   #include "libcamera/internal/pipeline_handler.h"
>   #include "libcamera/internal/pub_key.h"
> @@ -28,7 +29,7 @@ LOG_DECLARE_CATEGORY(IPAManager)
>   class IPAManager
>   {
>   public:
> -	IPAManager();
> +	IPAManager(const GlobalConfiguration &configuration);
>   	~IPAManager();
>   
>   	template<typename T>
> @@ -42,7 +43,8 @@ public:
>   		if (!m)
>   			return nullptr;
>   
> -		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m));
> +		const GlobalConfiguration &configuration = cm->_d()->configuration();
> +		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m, configuration), configuration);
>   		if (!proxy->isValid()) {
>   			LOG(IPAManager, Error) << "Failed to load proxy";
>   			return nullptr;
> @@ -66,7 +68,7 @@ private:
>   	IPAModule *module(PipelineHandler *pipe, uint32_t minVersion,
>   			  uint32_t maxVersion);
>   
> -	bool isSignatureValid(IPAModule *ipa) const;
> +	bool isSignatureValid(IPAModule *ipa, const GlobalConfiguration &configuration) const;
>   
>   	std::vector<std::unique_ptr<IPAModule>> modules_;
>   
> diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
> index 983bcc5fa..01cc5deff 100644
> --- a/include/libcamera/internal/ipa_proxy.h
> +++ b/include/libcamera/internal/ipa_proxy.h
> @@ -11,6 +11,8 @@
>   
>   #include <libcamera/ipa/ipa_interface.h>
>   
> +#include "libcamera/internal/global_configuration.h"
> +
>   namespace libcamera {
>   
>   class IPAModule;
> @@ -30,10 +32,11 @@ public:
>   	bool isValid() const { return valid_; }
>   
>   	std::string configurationFile(const std::string &name,
> +				      const GlobalConfiguration &configuration,
>   				      const std::string &fallbackName = std::string()) const;
>   
>   protected:
> -	std::string resolvePath(const std::string &file) const;
> +	std::string resolvePath(const std::string &file, const GlobalConfiguration &configuration) const;

I feel like `IPAProxy` could take the `GlobalConfiguration`
in the constructor?


>   
>   	bool valid_;
>   	ProxyState state_;
> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> index f3b4ec708..c47fd3677 100644
> --- a/src/libcamera/camera_manager.cpp
> +++ b/src/libcamera/camera_manager.cpp
> @@ -41,7 +41,7 @@ LOG_DEFINE_CATEGORY(Camera)
>   CameraManager::Private::Private()
>   	: initialized_(false)
>   {
> -	ipaManager_ = std::make_unique<IPAManager>();
> +	ipaManager_ = std::make_unique<IPAManager>(this->configuration());
>   }
>   
>   int CameraManager::Private::start()
> diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
> index 068ddd5a5..f7c69890c 100644
> --- a/src/libcamera/global_configuration.cpp
> +++ b/src/libcamera/global_configuration.cpp
> @@ -9,9 +9,11 @@
>   
>   #include <filesystem>
>   #include <memory>
> +#include <optional>
>   #include <string>
>   #include <string_view>
>   #include <sys/types.h>
> +#include <vector>
>   
>   #include <libcamera/base/file.h>
>   #include <libcamera/base/log.h>
> @@ -116,13 +118,22 @@ GlobalConfiguration::GlobalConfiguration()
>    */
>   
>   /**
> + * \fn std::optional<T> GlobalConfiguration::option(const std::string &confPath) const
>    * \brief Return value of the configuration option identified by \a confPath
>    * \param[in] confPath Sequence of the YAML section names (excluding
>    * `configuration') leading to the requested option separated by slashes
>    * \return A value if an item corresponding to \a confPath exists in the
>    * configuration file, no value otherwise
>    */
> -std::optional<std::string> GlobalConfiguration::option(const std::string &confPath) const
> +
> +/**
> + * \brief Return values of the configuration option identified by \a confPath
> + * \tparam T The type of the retrieved configuration value
> + * \param[in] confPath Sequence of the YAML section names (excluding
> + * `configuration') leading to the requested list option, separated by dots
> + * \return A vector of strings (empty one if the option is not found)
> + */
> +std::vector<std::string> GlobalConfiguration::listOption(const std::string &confPath) const
>   {
>   	const YamlObject *c = &configuration();
>   	for (auto part : utils::split(confPath, "/")) {
> @@ -130,7 +141,7 @@ std::optional<std::string> GlobalConfiguration::option(const std::string &confPa
>   		if (!*c)
>   			return {};
>   	}
> -	return c->get<std::string>();
> +	return c->getList<std::string>().value_or(std::vector<std::string>());
>   }
>   
>   /**
> @@ -157,7 +168,37 @@ std::optional<std::string> GlobalConfiguration::envOption(
>   	const char *envValue = utils::secure_getenv(envVariable);
>   	if (envValue)
>   		return std::optional{ std::string{ envValue } };
> -	return option(confPath);
> +	return option<std::string>(confPath);
> +}
> +
> +/**
> + * \brief Return values of the configuration option from a file or environment
> + * \param[in] envVariable Environment variable to get the value from
> + * \param[in] confPath The same as in GlobalConfiguration::option
> + *
> + * This helper looks first at the given environment variable and if it is
> + * defined (even if it is empty) then it splits its value by semicolons and
> + * returns the resulting list of strings. Otherwise it looks for \a confPath the
> + * same way as in GlobalConfiguration::option, value of which must be a list of
> + * strings.
> + *
> + * \note Support for using environment variables to configure libcamera behavior
> + * is provided here mostly for backward compatibility reasons. Introducing new
> + * configuration environment variables is discouraged.
> + *
> + * \return A vector of strings retrieved from the given environment option or
> + * configuration file (an empty vector is returned if nothing is found)

I think we want to distinguish "not present" and "empty". I could imagine
that someone might want to inhibit some behaviour by setting the env var
to empty. `envOption()` also differentiates the two. For example, with
the new "layers" proposal, I could image that we can set the layers in
a configuration file, etc, as well as an environmental variable. In that
case doing `LIBCAMERA_LAYERS= cam ...` might be useful for testing to
temporarily disable all layers.


> + */
> +std::vector<std::string> GlobalConfiguration::envListOption(
> +	const char *const envVariable,
> +	const std::string &confPath) const
> +{
> +	const char *envValue = utils::secure_getenv(envVariable);
> +	if (envValue) {
> +		auto items = utils::split(envValue, ":");
> +		return std::vector<std::string>(items.begin(), items.end());
> +	}
> +	return listOption(confPath);
>   }
>   
>   /**
> diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
> index 830750dcc..67c1b23d2 100644
> --- a/src/libcamera/ipa_manager.cpp
> +++ b/src/libcamera/ipa_manager.cpp
> @@ -9,6 +9,7 @@
>   
>   #include <algorithm>
>   #include <dirent.h>
> +#include <numeric>
>   #include <string.h>
>   #include <sys/types.h>
>   
> @@ -16,6 +17,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> +#include "libcamera/internal/global_configuration.h"
>   #include "libcamera/internal/ipa_module.h"
>   #include "libcamera/internal/ipa_proxy.h"
>   #include "libcamera/internal/pipeline_handler.h"
> @@ -101,7 +103,7 @@ LOG_DEFINE_CATEGORY(IPAManager)
>    * The IPAManager class is meant to only be instantiated once, by the
>    * CameraManager.
>    */
> -IPAManager::IPAManager()
> +IPAManager::IPAManager(const GlobalConfiguration &configuration)
>   {
>   #if HAVE_IPA_PUBKEY
>   	if (!pubKey_.isValid())
> @@ -111,18 +113,25 @@ IPAManager::IPAManager()
>   	unsigned int ipaCount = 0;
>   
>   	/* User-specified paths take precedence. */
> -	const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH");
> -	if (modulePaths) {
> -		for (const auto &dir : utils::split(modulePaths, ":")) {
> -			if (dir.empty())
> -				continue;
> +	const auto modulePaths =
> +		configuration.envListOption(
> +			"LIBCAMERA_IPA_MODULE_PATH", "ipa/module_paths");
> +	for (const auto &dir : modulePaths) {
> +		if (dir.empty())
> +			continue;
>   
> -			ipaCount += addDir(dir.c_str());
> -		}
> +		ipaCount += addDir(dir.c_str());
> +	}
>   
> -		if (!ipaCount)
> -			LOG(IPAManager, Warning)
> -				<< "No IPA found in '" << modulePaths << "'";
> +	if (!ipaCount) {
> +		std::string paths;
> +		if (!modulePaths.empty()) {
> +			paths = std::accumulate(std::next(modulePaths.begin()),
> +						modulePaths.end(),
> +						modulePaths[0],
> +						[](std::string s1, std::string s2) { return s1 + ":" + s2; });
> +		}
> +		LOG(IPAManager, Warning) << "No IPA found in '" << paths << "'";

   #include <libcamera/base/utils.h>
   ...
   LOG(IPAManager, Warning) << "No IPA found in '" << utils::join(modulePaths, ":") << "'";

?


>   	}
>   
>   	/*
> @@ -276,11 +285,13 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
>    */
>   #endif
>   
> -bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
> +bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa, const GlobalConfiguration &configuration) const

Maybe `configuration.option<bool>("ipa/force_isolation").value_or(false)` could be
saved into a member variable in the constructor? Then this won't need
the configuration parameter.


>   {
>   #if HAVE_IPA_PUBKEY
>   	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
> -	if (force && force[0] != '\0') {
> +	if ((force && force[0] != '\0') ||
> +	    (!force && configuration.option<bool>("ipa/force_isolation")
> +			       .value_or(false))) {
>   		LOG(IPAManager, Debug)
>   			<< "Isolation of IPA module " << ipa->path()
>   			<< " forced through environment variable";
> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
> index 9907b9615..77927b0d4 100644
> --- a/src/libcamera/ipa_proxy.cpp
> +++ b/src/libcamera/ipa_proxy.cpp
> @@ -14,6 +14,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> +#include "libcamera/internal/global_configuration.h"
>   #include "libcamera/internal/ipa_module.h"
>   
>   /**
> @@ -71,6 +72,7 @@ IPAProxy::~IPAProxy()
>   /**
>    * \brief Retrieve the absolute path to an IPA configuration file
>    * \param[in] name The configuration file name
> + * \param[in] configuration The global configuration
>    * \param[in] fallbackName The name of a fallback configuration file
>    *
>    * This function locates the configuration file for an IPA and returns its
> @@ -96,6 +98,7 @@ IPAProxy::~IPAProxy()
>    * no configuration file can be found
>    */
>   std::string IPAProxy::configurationFile(const std::string &name,
> +					const GlobalConfiguration &configuration,
>   					const std::string &fallbackName) const
>   {
>   	/*
> @@ -105,37 +108,37 @@ std::string IPAProxy::configurationFile(const std::string &name,
>   	const std::string ipaName = ipam_->info().name;
>   
>   	/*
> -	 * Start with any user override through the module-specific environment
> -	 * variable. Use the name of the IPA module up to the first '/' to
> -	 * construct the variable name.
> +	 * Start with any user override through the module-specific configuration or
> +	 * environment variable. Use the name of the IPA module up to the first '/'
> +	 * to construct the configuration and variable names.
>   	 */
> -	std::string ipaEnvName = ipaName.substr(0, ipaName.find('/'));
> +	std::string ipaBaseName = ipaName.substr(0, ipaName.find('/'));
> +	std::string ipaConfigName = "ipa/ipas/" + ipaBaseName + "/tuning_file";

I feel like we're unnecessarily limiting outself wrt. rpi here. Since the IPA
names are "rpi/vc4" and "rpi/pisp", thus `ipaBaseName == "rpi"`. Which means
that it is impossible to specify different tuning files for the different
platforms. I think this is fine for an env var, but for a configuration file
I think this flexibility is expected.

But I don't quite see the point of overriding the tuning file like this from a
configuration file. So maybe leaving just the env var override is sufficient?



> +	std::string ipaEnvName = ipaBaseName;
>   	std::transform(ipaEnvName.begin(), ipaEnvName.end(), ipaEnvName.begin(),
>   		       [](unsigned char c) { return std::toupper(c); });
>   	ipaEnvName = "LIBCAMERA_" + ipaEnvName + "_TUNING_FILE";
>   
> -	char const *configFromEnv = utils::secure_getenv(ipaEnvName.c_str());
> -	if (configFromEnv && *configFromEnv != '\0')
> -		return { configFromEnv };
> +	auto config = configuration.envOption(ipaEnvName.c_str(), ipaConfigName);
> +	if (config)
> +		return { config.value() };
>   
>   	struct stat statbuf;
>   	int ret;
>   
>   	/*
> -	 * Check the directory pointed to by the IPA config path environment
> -	 * variable next.
> +	 * Check the directory pointed to by the IPA config path next.
>   	 */
> -	const char *confPaths = utils::secure_getenv("LIBCAMERA_IPA_CONFIG_PATH");
> -	if (confPaths) {
> -		for (const auto &dir : utils::split(confPaths, ":")) {
> -			if (dir.empty())
> -				continue;
> -
> -			std::string confPath = dir + "/" + ipaName + "/" + name;
> -			ret = stat(confPath.c_str(), &statbuf);
> -			if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
> -				return confPath;
> -		}
> +	auto confPaths =
> +		configuration.envListOption(
> +			"LIBCAMERA_IPA_CONFIG_PATH", "ipa/config_paths");
> +	for (const auto &dir : confPaths) {
> +		if (dir.empty())
> +			continue;
> +		std::string confPath = dir + "/" + ipaName + "/" + name;
> +		ret = stat(confPath.c_str(), &statbuf);
> +		if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
> +			return confPath;
>   	}
>   
>   	std::string root = utils::libcameraSourcePath();
> @@ -178,12 +181,13 @@ std::string IPAProxy::configurationFile(const std::string &name,
>   		<< "Configuration file '" << name
>   		<< "' not found for IPA module '" << ipaName
>   		<< "', falling back to '" << fallbackName << "'";
> -	return configurationFile(fallbackName);
> +	return configurationFile(fallbackName, configuration);
>   }
>   
>   /**
>    * \brief Find a valid full path for a proxy worker for a given executable name
>    * \param[in] file File name of proxy worker executable
> + * \param[in] configuration The global configuration
>    *
>    * A proxy worker's executable could be found in either the global installation
>    * directory, or in the paths specified by the environment variable
> @@ -195,22 +199,22 @@ std::string IPAProxy::configurationFile(const std::string &name,
>    * \return The full path to the proxy worker executable, or an empty string if
>    * no valid executable path
>    */
> -std::string IPAProxy::resolvePath(const std::string &file) const
> +std::string IPAProxy::resolvePath(const std::string &file, const GlobalConfiguration &configuration) const
>   {
>   	std::string proxyFile = "/" + file;
>   
> -	/* Check env variable first. */
> -	const char *execPaths = utils::secure_getenv("LIBCAMERA_IPA_PROXY_PATH");
> -	if (execPaths) {
> -		for (const auto &dir : utils::split(execPaths, ":")) {
> -			if (dir.empty())
> -				continue;
> -
> -			std::string proxyPath = dir;
> -			proxyPath += proxyFile;
> -			if (!access(proxyPath.c_str(), X_OK))
> -				return proxyPath;
> -		}
> +	/* Check the configuration first. */
> +	const auto execPaths =
> +		configuration.envListOption(
> +			"LIBCAMERA_IPA_PROXY_PATH", "ipa/proxy_paths");
> +	for (const auto &dir : execPaths) {
> +		if (dir.empty())
> +			continue;
> +
> +		std::string proxyPath = dir;

std::string proxyPath = dir + proxyFile;


Regards,
Barnabás Pőcze


> +		proxyPath += proxyFile;
> +		if (!access(proxyPath.c_str(), X_OK))
> +			return proxyPath;
>   	}
>   
>   	/*
> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> index e31e3879d..723f7665b 100644
> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> @@ -1183,8 +1183,9 @@ int IPU3CameraData::loadIPA()
>   	 * The API tuning file is made from the sensor name. If the tuning file
>   	 * isn't found, fall back to the 'uncalibrated' file.
>   	 */
> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>   	std::string ipaTuningFile =
> -		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
> +		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
>   
>   	ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
>   			 sensorInfo, sensor->controls(), &ipaControls_);
> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> index 4acc091bd..d346fa25f 100644
> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> @@ -388,7 +388,9 @@ int MaliC55CameraData::loadIPA()
>   
>   	ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls);
>   
> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>   	std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml",
> +							    configuration,
>   							    "uncalibrated.yaml");
>   
>   	/* We need to inform the IPA of the sensor configuration */
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index 675f0a749..aded4cb43 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -40,6 +40,7 @@
>   #include "libcamera/internal/delayed_controls.h"
>   #include "libcamera/internal/device_enumerator.h"
>   #include "libcamera/internal/framebuffer.h"
> +#include "libcamera/internal/global_configuration.h"
>   #include "libcamera/internal/ipa_manager.h"
>   #include "libcamera/internal/media_device.h"
>   #include "libcamera/internal/media_pipeline.h"
> @@ -383,8 +384,9 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
>   	ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady);
>   
>   	/* The IPA tuning file is made from the sensor name. */
> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>   	std::string ipaTuningFile =
> -		ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
> +		ipa_->configurationFile(sensor_->model() + ".yaml", configuration, "uncalibrated.yaml");
>   
>   	IPACameraSensorInfo sensorInfo{};
>   	int ret = sensor_->sensorInfo(&sensorInfo);
> diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> index a316ef297..3da919ee7 100644
> --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> @@ -1157,7 +1157,8 @@ int CameraData::loadIPA(ipa::RPi::InitResult *result)
>   	std::string model = sensor_->model();
>   	if (isMonoSensor(sensor_))
>   		model += "_mono";
> -	std::string configurationFile = ipa_->configurationFile(model + ".json");
> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
> +	std::string configurationFile = ipa_->configurationFile(model + ".json", configuration);
>   
>   	IPASettings settings(configurationFile, sensor_->model());
>   	ipa::RPi::InitParams params;
> diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
> index 07273bd2b..555d51bb6 100644
> --- a/src/libcamera/pipeline/vimc/vimc.cpp
> +++ b/src/libcamera/pipeline/vimc/vimc.cpp
> @@ -494,7 +494,8 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
>   
>   	data->ipa_->paramsComputed.connect(data.get(), &VimcCameraData::paramsComputed);
>   
> -	std::string conf = data->ipa_->configurationFile("vimc.conf");
> +	const GlobalConfiguration &configuration = manager_->_d()->configuration();
> +	std::string conf = data->ipa_->configurationFile("vimc.conf", configuration);
>   	Flags<ipa::vimc::TestFlag> inFlags = ipa::vimc::TestFlag::Flag2;
>   	Flags<ipa::vimc::TestFlag> outFlags;
>   	data->ipa_->init(IPASettings{ conf, data->sensor_->model() },
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 28e2a360e..3ce354111 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -21,6 +21,7 @@
>   #include <libcamera/stream.h>
>   
>   #include "libcamera/internal/framebuffer.h"
> +#include "libcamera/internal/global_configuration.h"
>   #include "libcamera/internal/ipa_manager.h"
>   #include "libcamera/internal/software_isp/debayer_params.h"
>   
> @@ -130,8 +131,9 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>   	 * The API tuning file is made from the sensor name. If the tuning file
>   	 * isn't found, fall back to the 'uncalibrated' file.
>   	 */
> +	const GlobalConfiguration &configuration = pipe->cameraManager()->_d()->configuration();
>   	std::string ipaTuningFile =
> -		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
> +		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
>   
>   	IPACameraSensorInfo sensorInfo{};
>   	int ret = sensor->sensorInfo(&sensorInfo);
> diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
> index b81783664..1afcbf106 100644
> --- a/test/ipa/ipa_interface_test.cpp
> +++ b/test/ipa/ipa_interface_test.cpp
> @@ -106,7 +106,8 @@ protected:
>   		}
>   
>   		/* Test initialization of IPA module. */
> -		std::string conf = ipa_->configurationFile("vimc.conf");
> +		const GlobalConfiguration configuration;
> +		std::string conf = ipa_->configurationFile("vimc.conf", configuration);
>   		Flags<ipa::vimc::TestFlag> inFlags;
>   		Flags<ipa::vimc::TestFlag> outFlags;
>   		int ret = ipa_->init(IPASettings{ conf, "vimc" },
> diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> index 9a3aadbd2..71e920eb1 100644
> --- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> @@ -45,7 +45,7 @@ namespace {{ns}} {
>   {% endfor %}
>   {%- endif %}
>   
> -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
>   	: IPAProxy(ipam), isolate_(isolate),
>   	  controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
>   {
> @@ -54,7 +54,7 @@ namespace {{ns}} {
>   		<< ipam->path();
>   
>   	if (isolate_) {
> -		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy");
> +		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy", configuration);
>   		if (proxyWorkerPath.empty()) {
>   			LOG(IPAProxy, Error)
>   				<< "Failed to get proxy worker path";
> diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> index a0312a7c1..057c3ab03 100644
> --- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> @@ -37,7 +37,7 @@ namespace {{ns}} {
>   class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object
>   {
>   public:
> -	{{proxy_name}}(IPAModule *ipam, bool isolate);
> +	{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration);
>   	~{{proxy_name}}();
>   
>   {% for method in interface_main.methods %}
Milan Zamazal June 27, 2025, 9:04 p.m. UTC | #2
Hi Barnabás,

thank you for review.

Barnabás Pőcze <barnabas.pocze@ideasonboard.com> writes:

> Hi
>
> 2025. 06. 24. 10:36 keltezéssel, Milan Zamazal írta:
>> This patch adds configuration options for environment variables used in
>> the IPA proxy.  Two utility functions configuration retrieval functions
>> are added, to retrieve lists of values.
>> The configuration snippet:
>>    configuration:
>>      ipa:
>>        config_paths:
>>        - config path 1
>>        - config path 2
>>        - ...
>>        module_paths:
>>        - module path 1
>>        - module path 2
>>        - ...
>>        proxy_paths:
>>        - proxy path 1
>>        - proxy path 2
>>        - ...
>>        force_isolation: BOOL
>>        ipas:
>>          IPA_NAME:
>>            tuning_file: TUNING FILE
>> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> ---
>>   .../libcamera/internal/global_configuration.h | 35 ++++++++-
>>   include/libcamera/internal/ipa_manager.h      |  8 ++-
>>   include/libcamera/internal/ipa_proxy.h        |  5 +-
>>   src/libcamera/camera_manager.cpp              |  2 +-
>>   src/libcamera/global_configuration.cpp        | 47 +++++++++++-
>>   src/libcamera/ipa_manager.cpp                 | 37 ++++++----
>>   src/libcamera/ipa_proxy.cpp                   | 72 ++++++++++---------
>>   src/libcamera/pipeline/ipu3/ipu3.cpp          |  3 +-
>>   src/libcamera/pipeline/mali-c55/mali-c55.cpp  |  2 +
>>   src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  4 +-
>>   .../pipeline/rpi/common/pipeline_base.cpp     |  3 +-
>>   src/libcamera/pipeline/vimc/vimc.cpp          |  3 +-
>>   src/libcamera/software_isp/software_isp.cpp   |  4 +-
>>   test/ipa/ipa_interface_test.cpp               |  3 +-
>>   .../module_ipa_proxy.cpp.tmpl                 |  4 +-
>>   .../module_ipa_proxy.h.tmpl                   |  2 +-
>>   16 files changed, 169 insertions(+), 65 deletions(-)
>> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
>> index 357119628..cc436703e 100644
>> --- a/include/libcamera/internal/global_configuration.h
>> +++ b/include/libcamera/internal/global_configuration.h
>> @@ -11,6 +11,8 @@
>>   #include <optional>
>>   #include <string>
>>   +#include <libcamera/base/utils.h>
>> +
>>   #include "libcamera/internal/yaml_parser.h"
>>     namespace libcamera {
>> @@ -24,9 +26,40 @@ public:
>>     	unsigned int version() const;
>>   	Configuration configuration() const;
>> -	std::optional<std::string> option(const std::string &confPath) const;
>> +
>> +#ifndef __DOXYGEN__
>> +	template<typename T,
>> +		 std::enable_if_t<
>> +			 std::is_same_v<bool, T> ||
>> +			 std::is_same_v<double, T> ||
>> +			 std::is_same_v<int8_t, T> ||
>> +			 std::is_same_v<uint8_t, T> ||
>> +			 std::is_same_v<int16_t, T> ||
>> +			 std::is_same_v<uint16_t, T> ||
>> +			 std::is_same_v<int32_t, T> ||
>> +			 std::is_same_v<uint32_t, T> ||
>> +			 std::is_same_v<std::string, T> ||
>> +			 std::is_same_v<Size, T>> * = nullptr>
>
> Is this needed? `YamlObject::get()` does not have any constraints,
> so I think it can be omitted here as well?

Yes; YamlObject::get() used to have the constraints but doesn't have
them any more.

>> +#else
>> +	template<typename T>
>> +#endif
>> +	std::optional<T> option(const std::string &confPath) const
>> +	{
>> +		const YamlObject *c = &configuration();
>> +		for (auto part : utils::split(confPath, "/")) {
>> +			c = &(*c)[part];
>> +			if (!*c)
>> +				return {};
>> +		}
>> +		return c->get<T>();
>> +	}
>> +
>> +	std::vector<std::string> listOption(const std::string &confPath) const;
>>   	std::optional<std::string> envOption(const char *const envVariable,
>>   					     const std::string &confPath) const;
>> +	std::vector<std::string> envListOption(
>> +		const char *const envVariable,
>> +		const std::string &confPath) const;
>>     private:
>>   	bool loadFile(const std::filesystem::path &fileName);
>> diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
>> index a0d448cf9..8ad717919 100644
>> --- a/include/libcamera/internal/ipa_manager.h
>> +++ b/include/libcamera/internal/ipa_manager.h
>> @@ -17,6 +17,7 @@
>>   #include <libcamera/ipa/ipa_module_info.h>
>>     #include "libcamera/internal/camera_manager.h"
>> +#include "libcamera/internal/global_configuration.h"
>>   #include "libcamera/internal/ipa_module.h"
>>   #include "libcamera/internal/pipeline_handler.h"
>>   #include "libcamera/internal/pub_key.h"
>> @@ -28,7 +29,7 @@ LOG_DECLARE_CATEGORY(IPAManager)
>>   class IPAManager
>>   {
>>   public:
>> -	IPAManager();
>> +	IPAManager(const GlobalConfiguration &configuration);
>>   	~IPAManager();
>>     	template<typename T>
>> @@ -42,7 +43,8 @@ public:
>>   		if (!m)
>>   			return nullptr;
>>   -		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m));
>> +		const GlobalConfiguration &configuration = cm->_d()->configuration();
>> +		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m, configuration), configuration);
>>   		if (!proxy->isValid()) {
>>   			LOG(IPAManager, Error) << "Failed to load proxy";
>>   			return nullptr;
>> @@ -66,7 +68,7 @@ private:
>>   	IPAModule *module(PipelineHandler *pipe, uint32_t minVersion,
>>   			  uint32_t maxVersion);
>>   -	bool isSignatureValid(IPAModule *ipa) const;
>> +	bool isSignatureValid(IPAModule *ipa, const GlobalConfiguration &configuration) const;
>>     	std::vector<std::unique_ptr<IPAModule>> modules_;
>>   diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
>> index 983bcc5fa..01cc5deff 100644
>> --- a/include/libcamera/internal/ipa_proxy.h
>> +++ b/include/libcamera/internal/ipa_proxy.h
>> @@ -11,6 +11,8 @@
>>     #include <libcamera/ipa/ipa_interface.h>
>>   +#include "libcamera/internal/global_configuration.h"
>> +
>>   namespace libcamera {
>>     class IPAModule;
>> @@ -30,10 +32,11 @@ public:
>>   	bool isValid() const { return valid_; }
>>     	std::string configurationFile(const std::string &name,
>> +				      const GlobalConfiguration &configuration,
>>   				      const std::string &fallbackName = std::string()) const;
>>     protected:
>> -	std::string resolvePath(const std::string &file) const;
>> +	std::string resolvePath(const std::string &file, const GlobalConfiguration &configuration) const;
>
> I feel like `IPAProxy` could take the `GlobalConfiguration`
> in the constructor?

I.e. copy the configuration and changing std::unique_ptr<YamlObject>
inside to shared_ptr?  Possible, I think, and I don't have a particular
preference -- up to your judgement what's better.

>>     	bool valid_;
>>   	ProxyState state_;
>> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
>> index f3b4ec708..c47fd3677 100644
>> --- a/src/libcamera/camera_manager.cpp
>> +++ b/src/libcamera/camera_manager.cpp
>> @@ -41,7 +41,7 @@ LOG_DEFINE_CATEGORY(Camera)
>>   CameraManager::Private::Private()
>>   	: initialized_(false)
>>   {
>> -	ipaManager_ = std::make_unique<IPAManager>();
>> +	ipaManager_ = std::make_unique<IPAManager>(this->configuration());
>>   }
>>     int CameraManager::Private::start()
>> diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
>> index 068ddd5a5..f7c69890c 100644
>> --- a/src/libcamera/global_configuration.cpp
>> +++ b/src/libcamera/global_configuration.cpp
>> @@ -9,9 +9,11 @@
>>     #include <filesystem>
>>   #include <memory>
>> +#include <optional>
>>   #include <string>
>>   #include <string_view>
>>   #include <sys/types.h>
>> +#include <vector>
>>     #include <libcamera/base/file.h>
>>   #include <libcamera/base/log.h>
>> @@ -116,13 +118,22 @@ GlobalConfiguration::GlobalConfiguration()
>>    */
>>     /**
>> + * \fn std::optional<T> GlobalConfiguration::option(const std::string &confPath) const
>>    * \brief Return value of the configuration option identified by \a confPath
>>    * \param[in] confPath Sequence of the YAML section names (excluding
>>    * `configuration') leading to the requested option separated by slashes
>>    * \return A value if an item corresponding to \a confPath exists in the
>>    * configuration file, no value otherwise
>>    */
>> -std::optional<std::string> GlobalConfiguration::option(const std::string &confPath) const
>> +
>> +/**
>> + * \brief Return values of the configuration option identified by \a confPath
>> + * \tparam T The type of the retrieved configuration value
>> + * \param[in] confPath Sequence of the YAML section names (excluding
>> + * `configuration') leading to the requested list option, separated by dots
>> + * \return A vector of strings (empty one if the option is not found)
>> + */
>> +std::vector<std::string> GlobalConfiguration::listOption(const std::string &confPath) const
>>   {
>>   	const YamlObject *c = &configuration();
>>   	for (auto part : utils::split(confPath, "/")) {
>> @@ -130,7 +141,7 @@ std::optional<std::string> GlobalConfiguration::option(const std::string &confPa
>>   		if (!*c)
>>   			return {};
>>   	}
>> -	return c->get<std::string>();
>> +	return c->getList<std::string>().value_or(std::vector<std::string>());
>>   }
>>     /**
>> @@ -157,7 +168,37 @@ std::optional<std::string> GlobalConfiguration::envOption(
>>   	const char *envValue = utils::secure_getenv(envVariable);
>>   	if (envValue)
>>   		return std::optional{ std::string{ envValue } };
>> -	return option(confPath);
>> +	return option<std::string>(confPath);
>> +}
>> +
>> +/**
>> + * \brief Return values of the configuration option from a file or environment
>> + * \param[in] envVariable Environment variable to get the value from
>> + * \param[in] confPath The same as in GlobalConfiguration::option
>> + *
>> + * This helper looks first at the given environment variable and if it is
>> + * defined (even if it is empty) then it splits its value by semicolons and
>> + * returns the resulting list of strings. Otherwise it looks for \a confPath the
>> + * same way as in GlobalConfiguration::option, value of which must be a list of
>> + * strings.
>> + *
>> + * \note Support for using environment variables to configure libcamera behavior
>> + * is provided here mostly for backward compatibility reasons. Introducing new
>> + * configuration environment variables is discouraged.
>> + *
>> + * \return A vector of strings retrieved from the given environment option or
>> + * configuration file (an empty vector is returned if nothing is found)
>
> I think we want to distinguish "not present" and "empty". I could imagine
> that someone might want to inhibit some behaviour by setting the env var
> to empty.

Well, introducing new environment variables for configuration is
discouraged.

> `envOption()` also differentiates the two. For example, with the new
> "layers" proposal, I could image that we can set the layers in a
> configuration file, etc, as well as an environmental variable. In that
> case doing `LIBCAMERA_LAYERS= cam ...` might be useful for testing to
> temporarily disable all layers.

Hmm, sounds reasonable but I'm still not sure we want to encourage this.
But technically, I can change the method signature.

>> + */
>> +std::vector<std::string> GlobalConfiguration::envListOption(
>> +	const char *const envVariable,
>> +	const std::string &confPath) const
>> +{
>> +	const char *envValue = utils::secure_getenv(envVariable);
>> +	if (envValue) {
>> +		auto items = utils::split(envValue, ":");
>> +		return std::vector<std::string>(items.begin(), items.end());
>> +	}
>> +	return listOption(confPath);
>>   }
>>     /**
>> diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
>> index 830750dcc..67c1b23d2 100644
>> --- a/src/libcamera/ipa_manager.cpp
>> +++ b/src/libcamera/ipa_manager.cpp
>> @@ -9,6 +9,7 @@
>>     #include <algorithm>
>>   #include <dirent.h>
>> +#include <numeric>
>>   #include <string.h>
>>   #include <sys/types.h>
>>   @@ -16,6 +17,7 @@
>>   #include <libcamera/base/log.h>
>>   #include <libcamera/base/utils.h>
>>   +#include "libcamera/internal/global_configuration.h"
>>   #include "libcamera/internal/ipa_module.h"
>>   #include "libcamera/internal/ipa_proxy.h"
>>   #include "libcamera/internal/pipeline_handler.h"
>> @@ -101,7 +103,7 @@ LOG_DEFINE_CATEGORY(IPAManager)
>>    * The IPAManager class is meant to only be instantiated once, by the
>>    * CameraManager.
>>    */
>> -IPAManager::IPAManager()
>> +IPAManager::IPAManager(const GlobalConfiguration &configuration)
>>   {
>>   #if HAVE_IPA_PUBKEY
>>   	if (!pubKey_.isValid())
>> @@ -111,18 +113,25 @@ IPAManager::IPAManager()
>>   	unsigned int ipaCount = 0;
>>     	/* User-specified paths take precedence. */
>> -	const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH");
>> -	if (modulePaths) {
>> -		for (const auto &dir : utils::split(modulePaths, ":")) {
>> -			if (dir.empty())
>> -				continue;
>> +	const auto modulePaths =
>> +		configuration.envListOption(
>> +			"LIBCAMERA_IPA_MODULE_PATH", "ipa/module_paths");
>> +	for (const auto &dir : modulePaths) {
>> +		if (dir.empty())
>> +			continue;
>>   -			ipaCount += addDir(dir.c_str());
>> -		}
>> +		ipaCount += addDir(dir.c_str());
>> +	}
>>   -		if (!ipaCount)
>> -			LOG(IPAManager, Warning)
>> -				<< "No IPA found in '" << modulePaths << "'";
>> +	if (!ipaCount) {
>> +		std::string paths;
>> +		if (!modulePaths.empty()) {
>> +			paths = std::accumulate(std::next(modulePaths.begin()),
>> +						modulePaths.end(),
>> +						modulePaths[0],
>> +						[](std::string s1, std::string s2) { return s1 + ":" + s2; });
>> +		}
>> +		LOG(IPAManager, Warning) << "No IPA found in '" << paths << "'";
>
>   #include <libcamera/base/utils.h>
>   ...
>   LOG(IPAManager, Warning) << "No IPA found in '" << utils::join(modulePaths, ":") << "'";
>
> ?

OK.

>>   	}
>>     	/*
>> @@ -276,11 +285,13 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
>>    */
>>   #endif
>>   -bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
>> +bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa, const GlobalConfiguration &configuration) const
>
> Maybe `configuration.option<bool>("ipa/force_isolation").value_or(false)` could be
> saved into a member variable in the constructor? Then this won't need
> the configuration parameter.

Yes.  Assuming the `pipe' CameraManager instance is always the same that
was used to create the IPAManager instance.

>>   {
>>   #if HAVE_IPA_PUBKEY
>>   	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
>> -	if (force && force[0] != '\0') {
>> +	if ((force && force[0] != '\0') ||
>> +	    (!force && configuration.option<bool>("ipa/force_isolation")
>> +			       .value_or(false))) {
>>   		LOG(IPAManager, Debug)
>>   			<< "Isolation of IPA module " << ipa->path()
>>   			<< " forced through environment variable";
>> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
>> index 9907b9615..77927b0d4 100644
>> --- a/src/libcamera/ipa_proxy.cpp
>> +++ b/src/libcamera/ipa_proxy.cpp
>> @@ -14,6 +14,7 @@
>>   #include <libcamera/base/log.h>
>>   #include <libcamera/base/utils.h>
>>   +#include "libcamera/internal/global_configuration.h"
>>   #include "libcamera/internal/ipa_module.h"
>>     /**
>> @@ -71,6 +72,7 @@ IPAProxy::~IPAProxy()
>>   /**
>>    * \brief Retrieve the absolute path to an IPA configuration file
>>    * \param[in] name The configuration file name
>> + * \param[in] configuration The global configuration
>>    * \param[in] fallbackName The name of a fallback configuration file
>>    *
>>    * This function locates the configuration file for an IPA and returns its
>> @@ -96,6 +98,7 @@ IPAProxy::~IPAProxy()
>>    * no configuration file can be found
>>    */
>>   std::string IPAProxy::configurationFile(const std::string &name,
>> +					const GlobalConfiguration &configuration,
>>   					const std::string &fallbackName) const
>>   {
>>   	/*
>> @@ -105,37 +108,37 @@ std::string IPAProxy::configurationFile(const std::string &name,
>>   	const std::string ipaName = ipam_->info().name;
>>     	/*
>> -	 * Start with any user override through the module-specific environment
>> -	 * variable. Use the name of the IPA module up to the first '/' to
>> -	 * construct the variable name.
>> +	 * Start with any user override through the module-specific configuration or
>> +	 * environment variable. Use the name of the IPA module up to the first '/'
>> +	 * to construct the configuration and variable names.
>>   	 */
>> -	std::string ipaEnvName = ipaName.substr(0, ipaName.find('/'));
>> +	std::string ipaBaseName = ipaName.substr(0, ipaName.find('/'));
>> +	std::string ipaConfigName = "ipa/ipas/" + ipaBaseName + "/tuning_file";
>
> I feel like we're unnecessarily limiting outself wrt. rpi here. Since the IPA
> names are "rpi/vc4" and "rpi/pisp", thus `ipaBaseName == "rpi"`. Which means
> that it is impossible to specify different tuning files for the different
> platforms. I think this is fine for an env var, but for a configuration file
> I think this flexibility is expected.

Right; so rather than trimming, let's replace the slash with another
character here?

> But I don't quite see the point of overriding the tuning file like this from a
> configuration file. So maybe leaving just the env var override is sufficient?

I think the initial idea was to be able to configure everything in the
configuration file (why not?).  Logging is currently a noticeable
exception, although hopefully only "temporary".

>> +	std::string ipaEnvName = ipaBaseName;
>>   	std::transform(ipaEnvName.begin(), ipaEnvName.end(), ipaEnvName.begin(),
>>   		       [](unsigned char c) { return std::toupper(c); });
>>   	ipaEnvName = "LIBCAMERA_" + ipaEnvName + "_TUNING_FILE";
>>   -	char const *configFromEnv = utils::secure_getenv(ipaEnvName.c_str());
>> -	if (configFromEnv && *configFromEnv != '\0')
>> -		return { configFromEnv };
>> +	auto config = configuration.envOption(ipaEnvName.c_str(), ipaConfigName);
>> +	if (config)
>> +		return { config.value() };
>>     	struct stat statbuf;
>>   	int ret;
>>     	/*
>> -	 * Check the directory pointed to by the IPA config path environment
>> -	 * variable next.
>> +	 * Check the directory pointed to by the IPA config path next.
>>   	 */
>> -	const char *confPaths = utils::secure_getenv("LIBCAMERA_IPA_CONFIG_PATH");
>> -	if (confPaths) {
>> -		for (const auto &dir : utils::split(confPaths, ":")) {
>> -			if (dir.empty())
>> -				continue;
>> -
>> -			std::string confPath = dir + "/" + ipaName + "/" + name;
>> -			ret = stat(confPath.c_str(), &statbuf);
>> -			if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
>> -				return confPath;
>> -		}
>> +	auto confPaths =
>> +		configuration.envListOption(
>> +			"LIBCAMERA_IPA_CONFIG_PATH", "ipa/config_paths");
>> +	for (const auto &dir : confPaths) {
>> +		if (dir.empty())
>> +			continue;
>> +		std::string confPath = dir + "/" + ipaName + "/" + name;
>> +		ret = stat(confPath.c_str(), &statbuf);
>> +		if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
>> +			return confPath;
>>   	}
>>     	std::string root = utils::libcameraSourcePath();
>> @@ -178,12 +181,13 @@ std::string IPAProxy::configurationFile(const std::string &name,
>>   		<< "Configuration file '" << name
>>   		<< "' not found for IPA module '" << ipaName
>>   		<< "', falling back to '" << fallbackName << "'";
>> -	return configurationFile(fallbackName);
>> +	return configurationFile(fallbackName, configuration);
>>   }
>>     /**
>>    * \brief Find a valid full path for a proxy worker for a given executable name
>>    * \param[in] file File name of proxy worker executable
>> + * \param[in] configuration The global configuration
>>    *
>>    * A proxy worker's executable could be found in either the global installation
>>    * directory, or in the paths specified by the environment variable
>> @@ -195,22 +199,22 @@ std::string IPAProxy::configurationFile(const std::string &name,
>>    * \return The full path to the proxy worker executable, or an empty string if
>>    * no valid executable path
>>    */
>> -std::string IPAProxy::resolvePath(const std::string &file) const
>> +std::string IPAProxy::resolvePath(const std::string &file, const GlobalConfiguration &configuration) const
>>   {
>>   	std::string proxyFile = "/" + file;
>>   -	/* Check env variable first. */
>> -	const char *execPaths = utils::secure_getenv("LIBCAMERA_IPA_PROXY_PATH");
>> -	if (execPaths) {
>> -		for (const auto &dir : utils::split(execPaths, ":")) {
>> -			if (dir.empty())
>> -				continue;
>> -
>> -			std::string proxyPath = dir;
>> -			proxyPath += proxyFile;
>> -			if (!access(proxyPath.c_str(), X_OK))
>> -				return proxyPath;
>> -		}
>> +	/* Check the configuration first. */
>> +	const auto execPaths =
>> +		configuration.envListOption(
>> +			"LIBCAMERA_IPA_PROXY_PATH", "ipa/proxy_paths");
>> +	for (const auto &dir : execPaths) {
>> +		if (dir.empty())
>> +			continue;
>> +
>> +		std::string proxyPath = dir;
>
> std::string proxyPath = dir + proxyFile;

OK.

> Regards,
> Barnabás Pőcze
>
>
>> +		proxyPath += proxyFile;
>> +		if (!access(proxyPath.c_str(), X_OK))
>> +			return proxyPath;
>>   	}
>>     	/*
>> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
>> index e31e3879d..723f7665b 100644
>> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
>> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
>> @@ -1183,8 +1183,9 @@ int IPU3CameraData::loadIPA()
>>   	 * The API tuning file is made from the sensor name. If the tuning file
>>   	 * isn't found, fall back to the 'uncalibrated' file.
>>   	 */
>> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>>   	std::string ipaTuningFile =
>> -		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
>> +		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
>>     	ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
>>   			 sensorInfo, sensor->controls(), &ipaControls_);
>> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> index 4acc091bd..d346fa25f 100644
>> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
>> @@ -388,7 +388,9 @@ int MaliC55CameraData::loadIPA()
>>     	ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls);
>>   +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>>   	std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml",
>> +							    configuration,
>>   							    "uncalibrated.yaml");
>>     	/* We need to inform the IPA of the sensor configuration */
>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
>> index 675f0a749..aded4cb43 100644
>> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
>> @@ -40,6 +40,7 @@
>>   #include "libcamera/internal/delayed_controls.h"
>>   #include "libcamera/internal/device_enumerator.h"
>>   #include "libcamera/internal/framebuffer.h"
>> +#include "libcamera/internal/global_configuration.h"
>>   #include "libcamera/internal/ipa_manager.h"
>>   #include "libcamera/internal/media_device.h"
>>   #include "libcamera/internal/media_pipeline.h"
>> @@ -383,8 +384,9 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
>>   	ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady);
>>     	/* The IPA tuning file is made from the sensor name. */
>> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>>   	std::string ipaTuningFile =
>> -		ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
>> +		ipa_->configurationFile(sensor_->model() + ".yaml", configuration, "uncalibrated.yaml");
>>     	IPACameraSensorInfo sensorInfo{};
>>   	int ret = sensor_->sensorInfo(&sensorInfo);
>> diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
>> index a316ef297..3da919ee7 100644
>> --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
>> +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
>> @@ -1157,7 +1157,8 @@ int CameraData::loadIPA(ipa::RPi::InitResult *result)
>>   	std::string model = sensor_->model();
>>   	if (isMonoSensor(sensor_))
>>   		model += "_mono";
>> -	std::string configurationFile = ipa_->configurationFile(model + ".json");
>> +	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
>> +	std::string configurationFile = ipa_->configurationFile(model + ".json", configuration);
>>     	IPASettings settings(configurationFile, sensor_->model());
>>   	ipa::RPi::InitParams params;
>> diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
>> index 07273bd2b..555d51bb6 100644
>> --- a/src/libcamera/pipeline/vimc/vimc.cpp
>> +++ b/src/libcamera/pipeline/vimc/vimc.cpp
>> @@ -494,7 +494,8 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
>>     	data->ipa_->paramsComputed.connect(data.get(), &VimcCameraData::paramsComputed);
>>   -	std::string conf = data->ipa_->configurationFile("vimc.conf");
>> +	const GlobalConfiguration &configuration = manager_->_d()->configuration();
>> +	std::string conf = data->ipa_->configurationFile("vimc.conf", configuration);
>>   	Flags<ipa::vimc::TestFlag> inFlags = ipa::vimc::TestFlag::Flag2;
>>   	Flags<ipa::vimc::TestFlag> outFlags;
>>   	data->ipa_->init(IPASettings{ conf, data->sensor_->model() },
>> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
>> index 28e2a360e..3ce354111 100644
>> --- a/src/libcamera/software_isp/software_isp.cpp
>> +++ b/src/libcamera/software_isp/software_isp.cpp
>> @@ -21,6 +21,7 @@
>>   #include <libcamera/stream.h>
>>     #include "libcamera/internal/framebuffer.h"
>> +#include "libcamera/internal/global_configuration.h"
>>   #include "libcamera/internal/ipa_manager.h"
>>   #include "libcamera/internal/software_isp/debayer_params.h"
>>   @@ -130,8 +131,9 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>>   	 * The API tuning file is made from the sensor name. If the tuning file
>>   	 * isn't found, fall back to the 'uncalibrated' file.
>>   	 */
>> +	const GlobalConfiguration &configuration = pipe->cameraManager()->_d()->configuration();
>>   	std::string ipaTuningFile =
>> -		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
>> +		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
>>     	IPACameraSensorInfo sensorInfo{};
>>   	int ret = sensor->sensorInfo(&sensorInfo);
>> diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
>> index b81783664..1afcbf106 100644
>> --- a/test/ipa/ipa_interface_test.cpp
>> +++ b/test/ipa/ipa_interface_test.cpp
>> @@ -106,7 +106,8 @@ protected:
>>   		}
>>     		/* Test initialization of IPA module. */
>> -		std::string conf = ipa_->configurationFile("vimc.conf");
>> +		const GlobalConfiguration configuration;
>> +		std::string conf = ipa_->configurationFile("vimc.conf", configuration);
>>   		Flags<ipa::vimc::TestFlag> inFlags;
>>   		Flags<ipa::vimc::TestFlag> outFlags;
>>   		int ret = ipa_->init(IPASettings{ conf, "vimc" },
>> diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
>> index 9a3aadbd2..71e920eb1 100644
>> --- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
>> +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
>> @@ -45,7 +45,7 @@ namespace {{ns}} {
>>   {% endfor %}
>>   {%- endif %}
>>   -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
>> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
>>   	: IPAProxy(ipam), isolate_(isolate),
>>   	  controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
>>   {
>> @@ -54,7 +54,7 @@ namespace {{ns}} {
>>   		<< ipam->path();
>>     	if (isolate_) {
>> -		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy");
>> +		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy", configuration);
>>   		if (proxyWorkerPath.empty()) {
>>   			LOG(IPAProxy, Error)
>>   				<< "Failed to get proxy worker path";
>> diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
>> index a0312a7c1..057c3ab03 100644
>> --- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
>> +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
>> @@ -37,7 +37,7 @@ namespace {{ns}} {
>>   class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object
>>   {
>>   public:
>> -	{{proxy_name}}(IPAModule *ipam, bool isolate);
>> +	{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration);
>>   	~{{proxy_name}}();
>>     {% for method in interface_main.methods %}

Patch
diff mbox series

diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 357119628..cc436703e 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -11,6 +11,8 @@ 
 #include <optional>
 #include <string>
 
+#include <libcamera/base/utils.h>
+
 #include "libcamera/internal/yaml_parser.h"
 
 namespace libcamera {
@@ -24,9 +26,40 @@  public:
 
 	unsigned int version() const;
 	Configuration configuration() const;
-	std::optional<std::string> option(const std::string &confPath) const;
+
+#ifndef __DOXYGEN__
+	template<typename T,
+		 std::enable_if_t<
+			 std::is_same_v<bool, T> ||
+			 std::is_same_v<double, T> ||
+			 std::is_same_v<int8_t, T> ||
+			 std::is_same_v<uint8_t, T> ||
+			 std::is_same_v<int16_t, T> ||
+			 std::is_same_v<uint16_t, T> ||
+			 std::is_same_v<int32_t, T> ||
+			 std::is_same_v<uint32_t, T> ||
+			 std::is_same_v<std::string, T> ||
+			 std::is_same_v<Size, T>> * = nullptr>
+#else
+	template<typename T>
+#endif
+	std::optional<T> option(const std::string &confPath) const
+	{
+		const YamlObject *c = &configuration();
+		for (auto part : utils::split(confPath, "/")) {
+			c = &(*c)[part];
+			if (!*c)
+				return {};
+		}
+		return c->get<T>();
+	}
+
+	std::vector<std::string> listOption(const std::string &confPath) const;
 	std::optional<std::string> envOption(const char *const envVariable,
 					     const std::string &confPath) const;
+	std::vector<std::string> envListOption(
+		const char *const envVariable,
+		const std::string &confPath) const;
 
 private:
 	bool loadFile(const std::filesystem::path &fileName);
diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index a0d448cf9..8ad717919 100644
--- a/include/libcamera/internal/ipa_manager.h
+++ b/include/libcamera/internal/ipa_manager.h
@@ -17,6 +17,7 @@ 
 #include <libcamera/ipa/ipa_module_info.h>
 
 #include "libcamera/internal/camera_manager.h"
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/pub_key.h"
@@ -28,7 +29,7 @@  LOG_DECLARE_CATEGORY(IPAManager)
 class IPAManager
 {
 public:
-	IPAManager();
+	IPAManager(const GlobalConfiguration &configuration);
 	~IPAManager();
 
 	template<typename T>
@@ -42,7 +43,8 @@  public:
 		if (!m)
 			return nullptr;
 
-		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m));
+		const GlobalConfiguration &configuration = cm->_d()->configuration();
+		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m, configuration), configuration);
 		if (!proxy->isValid()) {
 			LOG(IPAManager, Error) << "Failed to load proxy";
 			return nullptr;
@@ -66,7 +68,7 @@  private:
 	IPAModule *module(PipelineHandler *pipe, uint32_t minVersion,
 			  uint32_t maxVersion);
 
-	bool isSignatureValid(IPAModule *ipa) const;
+	bool isSignatureValid(IPAModule *ipa, const GlobalConfiguration &configuration) const;
 
 	std::vector<std::unique_ptr<IPAModule>> modules_;
 
diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
index 983bcc5fa..01cc5deff 100644
--- a/include/libcamera/internal/ipa_proxy.h
+++ b/include/libcamera/internal/ipa_proxy.h
@@ -11,6 +11,8 @@ 
 
 #include <libcamera/ipa/ipa_interface.h>
 
+#include "libcamera/internal/global_configuration.h"
+
 namespace libcamera {
 
 class IPAModule;
@@ -30,10 +32,11 @@  public:
 	bool isValid() const { return valid_; }
 
 	std::string configurationFile(const std::string &name,
+				      const GlobalConfiguration &configuration,
 				      const std::string &fallbackName = std::string()) const;
 
 protected:
-	std::string resolvePath(const std::string &file) const;
+	std::string resolvePath(const std::string &file, const GlobalConfiguration &configuration) const;
 
 	bool valid_;
 	ProxyState state_;
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index f3b4ec708..c47fd3677 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -41,7 +41,7 @@  LOG_DEFINE_CATEGORY(Camera)
 CameraManager::Private::Private()
 	: initialized_(false)
 {
-	ipaManager_ = std::make_unique<IPAManager>();
+	ipaManager_ = std::make_unique<IPAManager>(this->configuration());
 }
 
 int CameraManager::Private::start()
diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
index 068ddd5a5..f7c69890c 100644
--- a/src/libcamera/global_configuration.cpp
+++ b/src/libcamera/global_configuration.cpp
@@ -9,9 +9,11 @@ 
 
 #include <filesystem>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <sys/types.h>
+#include <vector>
 
 #include <libcamera/base/file.h>
 #include <libcamera/base/log.h>
@@ -116,13 +118,22 @@  GlobalConfiguration::GlobalConfiguration()
  */
 
 /**
+ * \fn std::optional<T> GlobalConfiguration::option(const std::string &confPath) const
  * \brief Return value of the configuration option identified by \a confPath
  * \param[in] confPath Sequence of the YAML section names (excluding
  * `configuration') leading to the requested option separated by slashes
  * \return A value if an item corresponding to \a confPath exists in the
  * configuration file, no value otherwise
  */
-std::optional<std::string> GlobalConfiguration::option(const std::string &confPath) const
+
+/**
+ * \brief Return values of the configuration option identified by \a confPath
+ * \tparam T The type of the retrieved configuration value
+ * \param[in] confPath Sequence of the YAML section names (excluding
+ * `configuration') leading to the requested list option, separated by dots
+ * \return A vector of strings (empty one if the option is not found)
+ */
+std::vector<std::string> GlobalConfiguration::listOption(const std::string &confPath) const
 {
 	const YamlObject *c = &configuration();
 	for (auto part : utils::split(confPath, "/")) {
@@ -130,7 +141,7 @@  std::optional<std::string> GlobalConfiguration::option(const std::string &confPa
 		if (!*c)
 			return {};
 	}
-	return c->get<std::string>();
+	return c->getList<std::string>().value_or(std::vector<std::string>());
 }
 
 /**
@@ -157,7 +168,37 @@  std::optional<std::string> GlobalConfiguration::envOption(
 	const char *envValue = utils::secure_getenv(envVariable);
 	if (envValue)
 		return std::optional{ std::string{ envValue } };
-	return option(confPath);
+	return option<std::string>(confPath);
+}
+
+/**
+ * \brief Return values of the configuration option from a file or environment
+ * \param[in] envVariable Environment variable to get the value from
+ * \param[in] confPath The same as in GlobalConfiguration::option
+ *
+ * This helper looks first at the given environment variable and if it is
+ * defined (even if it is empty) then it splits its value by semicolons and
+ * returns the resulting list of strings. Otherwise it looks for \a confPath the
+ * same way as in GlobalConfiguration::option, value of which must be a list of
+ * strings.
+ *
+ * \note Support for using environment variables to configure libcamera behavior
+ * is provided here mostly for backward compatibility reasons. Introducing new
+ * configuration environment variables is discouraged.
+ *
+ * \return A vector of strings retrieved from the given environment option or
+ * configuration file (an empty vector is returned if nothing is found)
+ */
+std::vector<std::string> GlobalConfiguration::envListOption(
+	const char *const envVariable,
+	const std::string &confPath) const
+{
+	const char *envValue = utils::secure_getenv(envVariable);
+	if (envValue) {
+		auto items = utils::split(envValue, ":");
+		return std::vector<std::string>(items.begin(), items.end());
+	}
+	return listOption(confPath);
 }
 
 /**
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index 830750dcc..67c1b23d2 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -9,6 +9,7 @@ 
 
 #include <algorithm>
 #include <dirent.h>
+#include <numeric>
 #include <string.h>
 #include <sys/types.h>
 
@@ -16,6 +17,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 #include "libcamera/internal/ipa_proxy.h"
 #include "libcamera/internal/pipeline_handler.h"
@@ -101,7 +103,7 @@  LOG_DEFINE_CATEGORY(IPAManager)
  * The IPAManager class is meant to only be instantiated once, by the
  * CameraManager.
  */
-IPAManager::IPAManager()
+IPAManager::IPAManager(const GlobalConfiguration &configuration)
 {
 #if HAVE_IPA_PUBKEY
 	if (!pubKey_.isValid())
@@ -111,18 +113,25 @@  IPAManager::IPAManager()
 	unsigned int ipaCount = 0;
 
 	/* User-specified paths take precedence. */
-	const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH");
-	if (modulePaths) {
-		for (const auto &dir : utils::split(modulePaths, ":")) {
-			if (dir.empty())
-				continue;
+	const auto modulePaths =
+		configuration.envListOption(
+			"LIBCAMERA_IPA_MODULE_PATH", "ipa/module_paths");
+	for (const auto &dir : modulePaths) {
+		if (dir.empty())
+			continue;
 
-			ipaCount += addDir(dir.c_str());
-		}
+		ipaCount += addDir(dir.c_str());
+	}
 
-		if (!ipaCount)
-			LOG(IPAManager, Warning)
-				<< "No IPA found in '" << modulePaths << "'";
+	if (!ipaCount) {
+		std::string paths;
+		if (!modulePaths.empty()) {
+			paths = std::accumulate(std::next(modulePaths.begin()),
+						modulePaths.end(),
+						modulePaths[0],
+						[](std::string s1, std::string s2) { return s1 + ":" + s2; });
+		}
+		LOG(IPAManager, Warning) << "No IPA found in '" << paths << "'";
 	}
 
 	/*
@@ -276,11 +285,13 @@  IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
  */
 #endif
 
-bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
+bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa, const GlobalConfiguration &configuration) const
 {
 #if HAVE_IPA_PUBKEY
 	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
-	if (force && force[0] != '\0') {
+	if ((force && force[0] != '\0') ||
+	    (!force && configuration.option<bool>("ipa/force_isolation")
+			       .value_or(false))) {
 		LOG(IPAManager, Debug)
 			<< "Isolation of IPA module " << ipa->path()
 			<< " forced through environment variable";
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 9907b9615..77927b0d4 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -14,6 +14,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 
 /**
@@ -71,6 +72,7 @@  IPAProxy::~IPAProxy()
 /**
  * \brief Retrieve the absolute path to an IPA configuration file
  * \param[in] name The configuration file name
+ * \param[in] configuration The global configuration
  * \param[in] fallbackName The name of a fallback configuration file
  *
  * This function locates the configuration file for an IPA and returns its
@@ -96,6 +98,7 @@  IPAProxy::~IPAProxy()
  * no configuration file can be found
  */
 std::string IPAProxy::configurationFile(const std::string &name,
+					const GlobalConfiguration &configuration,
 					const std::string &fallbackName) const
 {
 	/*
@@ -105,37 +108,37 @@  std::string IPAProxy::configurationFile(const std::string &name,
 	const std::string ipaName = ipam_->info().name;
 
 	/*
-	 * Start with any user override through the module-specific environment
-	 * variable. Use the name of the IPA module up to the first '/' to
-	 * construct the variable name.
+	 * Start with any user override through the module-specific configuration or
+	 * environment variable. Use the name of the IPA module up to the first '/'
+	 * to construct the configuration and variable names.
 	 */
-	std::string ipaEnvName = ipaName.substr(0, ipaName.find('/'));
+	std::string ipaBaseName = ipaName.substr(0, ipaName.find('/'));
+	std::string ipaConfigName = "ipa/ipas/" + ipaBaseName + "/tuning_file";
+	std::string ipaEnvName = ipaBaseName;
 	std::transform(ipaEnvName.begin(), ipaEnvName.end(), ipaEnvName.begin(),
 		       [](unsigned char c) { return std::toupper(c); });
 	ipaEnvName = "LIBCAMERA_" + ipaEnvName + "_TUNING_FILE";
 
-	char const *configFromEnv = utils::secure_getenv(ipaEnvName.c_str());
-	if (configFromEnv && *configFromEnv != '\0')
-		return { configFromEnv };
+	auto config = configuration.envOption(ipaEnvName.c_str(), ipaConfigName);
+	if (config)
+		return { config.value() };
 
 	struct stat statbuf;
 	int ret;
 
 	/*
-	 * Check the directory pointed to by the IPA config path environment
-	 * variable next.
+	 * Check the directory pointed to by the IPA config path next.
 	 */
-	const char *confPaths = utils::secure_getenv("LIBCAMERA_IPA_CONFIG_PATH");
-	if (confPaths) {
-		for (const auto &dir : utils::split(confPaths, ":")) {
-			if (dir.empty())
-				continue;
-
-			std::string confPath = dir + "/" + ipaName + "/" + name;
-			ret = stat(confPath.c_str(), &statbuf);
-			if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
-				return confPath;
-		}
+	auto confPaths =
+		configuration.envListOption(
+			"LIBCAMERA_IPA_CONFIG_PATH", "ipa/config_paths");
+	for (const auto &dir : confPaths) {
+		if (dir.empty())
+			continue;
+		std::string confPath = dir + "/" + ipaName + "/" + name;
+		ret = stat(confPath.c_str(), &statbuf);
+		if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
+			return confPath;
 	}
 
 	std::string root = utils::libcameraSourcePath();
@@ -178,12 +181,13 @@  std::string IPAProxy::configurationFile(const std::string &name,
 		<< "Configuration file '" << name
 		<< "' not found for IPA module '" << ipaName
 		<< "', falling back to '" << fallbackName << "'";
-	return configurationFile(fallbackName);
+	return configurationFile(fallbackName, configuration);
 }
 
 /**
  * \brief Find a valid full path for a proxy worker for a given executable name
  * \param[in] file File name of proxy worker executable
+ * \param[in] configuration The global configuration
  *
  * A proxy worker's executable could be found in either the global installation
  * directory, or in the paths specified by the environment variable
@@ -195,22 +199,22 @@  std::string IPAProxy::configurationFile(const std::string &name,
  * \return The full path to the proxy worker executable, or an empty string if
  * no valid executable path
  */
-std::string IPAProxy::resolvePath(const std::string &file) const
+std::string IPAProxy::resolvePath(const std::string &file, const GlobalConfiguration &configuration) const
 {
 	std::string proxyFile = "/" + file;
 
-	/* Check env variable first. */
-	const char *execPaths = utils::secure_getenv("LIBCAMERA_IPA_PROXY_PATH");
-	if (execPaths) {
-		for (const auto &dir : utils::split(execPaths, ":")) {
-			if (dir.empty())
-				continue;
-
-			std::string proxyPath = dir;
-			proxyPath += proxyFile;
-			if (!access(proxyPath.c_str(), X_OK))
-				return proxyPath;
-		}
+	/* Check the configuration first. */
+	const auto execPaths =
+		configuration.envListOption(
+			"LIBCAMERA_IPA_PROXY_PATH", "ipa/proxy_paths");
+	for (const auto &dir : execPaths) {
+		if (dir.empty())
+			continue;
+
+		std::string proxyPath = dir;
+		proxyPath += proxyFile;
+		if (!access(proxyPath.c_str(), X_OK))
+			return proxyPath;
 	}
 
 	/*
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index e31e3879d..723f7665b 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -1183,8 +1183,9 @@  int IPU3CameraData::loadIPA()
 	 * The API tuning file is made from the sensor name. If the tuning file
 	 * isn't found, fall back to the 'uncalibrated' file.
 	 */
+	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
 	std::string ipaTuningFile =
-		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
+		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
 
 	ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
 			 sensorInfo, sensor->controls(), &ipaControls_);
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 4acc091bd..d346fa25f 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -388,7 +388,9 @@  int MaliC55CameraData::loadIPA()
 
 	ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls);
 
+	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
 	std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml",
+							    configuration,
 							    "uncalibrated.yaml");
 
 	/* We need to inform the IPA of the sensor configuration */
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 675f0a749..aded4cb43 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -40,6 +40,7 @@ 
 #include "libcamera/internal/delayed_controls.h"
 #include "libcamera/internal/device_enumerator.h"
 #include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_manager.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/media_pipeline.h"
@@ -383,8 +384,9 @@  int RkISP1CameraData::loadIPA(unsigned int hwRevision)
 	ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady);
 
 	/* The IPA tuning file is made from the sensor name. */
+	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
 	std::string ipaTuningFile =
-		ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
+		ipa_->configurationFile(sensor_->model() + ".yaml", configuration, "uncalibrated.yaml");
 
 	IPACameraSensorInfo sensorInfo{};
 	int ret = sensor_->sensorInfo(&sensorInfo);
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index a316ef297..3da919ee7 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -1157,7 +1157,8 @@  int CameraData::loadIPA(ipa::RPi::InitResult *result)
 	std::string model = sensor_->model();
 	if (isMonoSensor(sensor_))
 		model += "_mono";
-	std::string configurationFile = ipa_->configurationFile(model + ".json");
+	const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration();
+	std::string configurationFile = ipa_->configurationFile(model + ".json", configuration);
 
 	IPASettings settings(configurationFile, sensor_->model());
 	ipa::RPi::InitParams params;
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index 07273bd2b..555d51bb6 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -494,7 +494,8 @@  bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
 
 	data->ipa_->paramsComputed.connect(data.get(), &VimcCameraData::paramsComputed);
 
-	std::string conf = data->ipa_->configurationFile("vimc.conf");
+	const GlobalConfiguration &configuration = manager_->_d()->configuration();
+	std::string conf = data->ipa_->configurationFile("vimc.conf", configuration);
 	Flags<ipa::vimc::TestFlag> inFlags = ipa::vimc::TestFlag::Flag2;
 	Flags<ipa::vimc::TestFlag> outFlags;
 	data->ipa_->init(IPASettings{ conf, data->sensor_->model() },
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 28e2a360e..3ce354111 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -21,6 +21,7 @@ 
 #include <libcamera/stream.h>
 
 #include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_manager.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
 
@@ -130,8 +131,9 @@  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 	 * The API tuning file is made from the sensor name. If the tuning file
 	 * isn't found, fall back to the 'uncalibrated' file.
 	 */
+	const GlobalConfiguration &configuration = pipe->cameraManager()->_d()->configuration();
 	std::string ipaTuningFile =
-		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
+		ipa_->configurationFile(sensor->model() + ".yaml", configuration, "uncalibrated.yaml");
 
 	IPACameraSensorInfo sensorInfo{};
 	int ret = sensor->sensorInfo(&sensorInfo);
diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
index b81783664..1afcbf106 100644
--- a/test/ipa/ipa_interface_test.cpp
+++ b/test/ipa/ipa_interface_test.cpp
@@ -106,7 +106,8 @@  protected:
 		}
 
 		/* Test initialization of IPA module. */
-		std::string conf = ipa_->configurationFile("vimc.conf");
+		const GlobalConfiguration configuration;
+		std::string conf = ipa_->configurationFile("vimc.conf", configuration);
 		Flags<ipa::vimc::TestFlag> inFlags;
 		Flags<ipa::vimc::TestFlag> outFlags;
 		int ret = ipa_->init(IPASettings{ conf, "vimc" },
diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
index 9a3aadbd2..71e920eb1 100644
--- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
@@ -45,7 +45,7 @@  namespace {{ns}} {
 {% endfor %}
 {%- endif %}
 
-{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
+{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
 	: IPAProxy(ipam), isolate_(isolate),
 	  controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
 {
@@ -54,7 +54,7 @@  namespace {{ns}} {
 		<< ipam->path();
 
 	if (isolate_) {
-		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy");
+		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy", configuration);
 		if (proxyWorkerPath.empty()) {
 			LOG(IPAProxy, Error)
 				<< "Failed to get proxy worker path";
diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
index a0312a7c1..057c3ab03 100644
--- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
@@ -37,7 +37,7 @@  namespace {{ns}} {
 class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object
 {
 public:
-	{{proxy_name}}(IPAModule *ipam, bool isolate);
+	{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration);
 	~{{proxy_name}}();
 
 {% for method in interface_main.methods %}