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

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

Commit Message

Milan Zamazal July 29, 2025, 7:31 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

LIBCAMERA_<IPA_NAME>_TUNING_FILE remains configurable only via the
environment variable; this is supposed to be used only for testing and
debugging and it's not clear what to do about IPA names like "rpi/vc4"
and "rpi/pisp" exactly.

There are two ways to pass the configuration to the places where it is
needed: Either to pass it as an argument to the method calls that need
it, or to pass it to the class constructors and extract the needed
configuration from there.  This patch uses the second method as it is
less polluting the code.

Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 .../libcamera/internal/global_configuration.h | 21 +++++++-
 include/libcamera/internal/ipa_manager.h      |  7 ++-
 include/libcamera/internal/ipa_proxy.h        |  8 ++-
 src/libcamera/camera_manager.cpp              |  2 +-
 src/libcamera/global_configuration.cpp        | 47 +++++++++++++++--
 src/libcamera/ipa_manager.cpp                 | 39 ++++++++------
 src/libcamera/ipa_proxy.cpp                   | 51 +++++++++----------
 .../module_ipa_proxy.cpp.tmpl                 |  4 +-
 .../module_ipa_proxy.h.tmpl                   |  2 +-
 9 files changed, 128 insertions(+), 53 deletions(-)

Comments

Paul Elder Sept. 9, 2025, 7:56 a.m. UTC | #1
Hi Milan,

Thanks for the patch.

Quoting Milan Zamazal (2025-07-29 16:31:53)
> This patch adds configuration options for environment variables used in
> the IPA proxy.  Two utility functions configuration retrieval functions

s/Two.*$/Two utility functions for retrieving lists of configuration values/

> are added, to retrieve lists of values.

s/,.*$/\./

Personally I feel like the retrieval functions should just all be added in
3/12. Then in this patch we can just plumb it into the IPAs.

> 
> 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
> 
> LIBCAMERA_<IPA_NAME>_TUNING_FILE remains configurable only via the
> environment variable; this is supposed to be used only for testing and
> debugging and it's not clear what to do about IPA names like "rpi/vc4"
> and "rpi/pisp" exactly.

I thought it would be fine (or even desirable) to allow configuring the path to
the tuning files in the configuration file? It does make sense to keep the
environment variable though.

> 
> There are two ways to pass the configuration to the places where it is
> needed: Either to pass it as an argument to the method calls that need
> it, or to pass it to the class constructors and extract the needed
> configuration from there.  This patch uses the second method as it is
> less polluting the code.
> 
> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>  .../libcamera/internal/global_configuration.h | 21 +++++++-
>  include/libcamera/internal/ipa_manager.h      |  7 ++-
>  include/libcamera/internal/ipa_proxy.h        |  8 ++-
>  src/libcamera/camera_manager.cpp              |  2 +-
>  src/libcamera/global_configuration.cpp        | 47 +++++++++++++++--
>  src/libcamera/ipa_manager.cpp                 | 39 ++++++++------
>  src/libcamera/ipa_proxy.cpp                   | 51 +++++++++----------
>  .../module_ipa_proxy.cpp.tmpl                 |  4 +-
>  .../module_ipa_proxy.h.tmpl                   |  2 +-
>  9 files changed, 128 insertions(+), 53 deletions(-)
> 
> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
> index 78bebff9f..3b500ee29 100644
> --- a/include/libcamera/internal/global_configuration.h
> +++ b/include/libcamera/internal/global_configuration.h
> @@ -12,6 +12,8 @@
>  #include <string>
>  #include <string_view>
>  
> +#include <libcamera/base/utils.h>
> +
>  #include "libcamera/internal/yaml_parser.h"
>  
>  namespace libcamera {
> @@ -25,11 +27,28 @@ public:
>  
>         unsigned int version() const;
>         Configuration configuration() const;
> -       std::optional<std::string> option(
> +
> +       template<typename T>
> +       std::optional<T> option(
> +               const std::initializer_list<std::string_view> confPath) const
> +       {
> +               const YamlObject *c = &configuration();
> +               for (auto part : confPath) {
> +                       c = &(*c)[part];
> +                       if (!*c)
> +                               return {};
> +               }
> +               return c->get<T>();
> +       }
> +
> +       std::optional<std::vector<std::string>> listOption(
>                 const std::initializer_list<std::string_view> confPath) const;
>         std::optional<std::string> envOption(
>                 const char *const envVariable,
>                 const std::initializer_list<std::string_view> confPath) const;
> +       std::optional<std::vector<std::string>> envListOption(
> +               const char *const envVariable,
> +               const std::initializer_list<std::string_view> 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..b0b44c74a 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);

Ok so we pass it in during construction and everybody that needs it keeps a
pointer to it, that looks good.

>                 if (!proxy->isValid()) {
>                         LOG(IPAManager, Error) << "Failed to load proxy";
>                         return nullptr;
> @@ -73,6 +75,7 @@ private:
>  #if HAVE_IPA_PUBKEY
>         static const uint8_t publicKeyData_[];
>         static const PubKey pubKey_;
> +       bool forceIsolation_;
>  #endif
>  };
>  
> diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
> index 983bcc5fa..f1865d67e 100644
> --- a/include/libcamera/internal/ipa_proxy.h
> +++ b/include/libcamera/internal/ipa_proxy.h
> @@ -7,10 +7,14 @@
>  
>  #pragma once
>  
> +#include <optional>
>  #include <string>
> +#include <vector>
>  
>  #include <libcamera/ipa/ipa_interface.h>
>  
> +#include "libcamera/internal/global_configuration.h"
> +
>  namespace libcamera {
>  
>  class IPAModule;
> @@ -24,7 +28,7 @@ public:
>                 ProxyRunning,
>         };
>  
> -       IPAProxy(IPAModule *ipam);
> +       IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
>         ~IPAProxy();
>  
>         bool isValid() const { return valid_; }
> @@ -40,6 +44,8 @@ protected:
>  
>  private:
>         IPAModule *ipam_;
> +       std::vector<std::string> configPaths_;
> +       std::vector<std::string> execPaths_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> index dca3d9a83..64df62444 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 5be0f0701..1371392c5 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>
> @@ -114,13 +116,22 @@ GlobalConfiguration::GlobalConfiguration()
>   */
>  
>  /**
> + * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &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
>   * \return A value if an item corresponding to \a confPath exists in the
>   * configuration file, no value otherwise
>   */
> -std::optional<std::string> GlobalConfiguration::option(
> +
> +/**
> + * \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 or no value if not found
> + */
> +std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
>         const std::initializer_list<std::string_view> confPath) const
>  {
>         const YamlObject *c = &configuration();
> @@ -129,7 +140,7 @@ std::optional<std::string> GlobalConfiguration::option(
>                 if (!*c)
>                         return {};
>         }
> -       return c->get<std::string>();
> +       return c->getList<std::string>();
>  }
>  
>  /**
> @@ -156,7 +167,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.

Same comments from 3/12.

> + *
> + * \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 or no value if not found; the vector may be empty
> + */
> +std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
> +       const char *const envVariable,
> +       const std::initializer_list<std::string_view> 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..000d56efa 100644
> --- a/src/libcamera/ipa_manager.cpp
> +++ b/src/libcamera/ipa_manager.cpp
> @@ -9,13 +9,17 @@
>  
>  #include <algorithm>
>  #include <dirent.h>
> +#include <numeric>
>  #include <string.h>
> +#include <string>
>  #include <sys/types.h>
> +#include <vector>
>  
>  #include <libcamera/base/file.h>
>  #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,30 +105,36 @@ 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())
>                 LOG(IPAManager, Warning) << "Public key not valid";
> +
> +       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
> +       forceIsolation_ = (force && force[0] != '\0') ||
> +                         (!force && configuration.option<bool>({ "ipa", "force_isolation" })
> +                                            .value_or(false));

Although the environment variable doesn't let us do it, I wonder if there's
value in letting the configuration file configure which IPAs we want to
force-isolate and which we don't.

Also is there a reason why we don't use envOption() here? It is because we
can't get a bool out of env?

>  #endif
>  
>         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;
> -
> -                       ipaCount += addDir(dir.c_str());
> -               }
> +       const auto modulePaths =
> +               configuration.envListOption(
> +                       "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
> +       for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
> +               if (dir.empty())
> +                       continue;
>  
> -               if (!ipaCount)
> -                       LOG(IPAManager, Warning)
> -                               << "No IPA found in '" << modulePaths << "'";
> +               ipaCount += addDir(dir.c_str());
>         }
>  
> +       if (!ipaCount)
> +               LOG(IPAManager, Warning) << "No IPA found in '"
> +                                        << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
> +                                        << "'";
> +

Ok, looks good.

>         /*
>          * When libcamera is used before it is installed, load IPAs from the
>          * same build directory as the libcamera library itself.
> @@ -279,11 +289,10 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
>  bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
>  {
>  #if HAVE_IPA_PUBKEY
> -       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
> -       if (force && force[0] != '\0') {
> +       if (forceIsolation_) {
>                 LOG(IPAManager, Debug)
>                         << "Isolation of IPA module " << ipa->path()
> -                       << " forced through environment variable";
> +                       << " forced through configuration";
>                 return false;
>         }
>  
> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
> index 9907b9615..b845e9086 100644
> --- a/src/libcamera/ipa_proxy.cpp
> +++ b/src/libcamera/ipa_proxy.cpp
> @@ -7,13 +7,16 @@
>  
>  #include "libcamera/internal/ipa_proxy.h"
>  
> +#include <string>
>  #include <sys/stat.h>
>  #include <sys/types.h>
>  #include <unistd.h>
> +#include <vector>
>  
>  #include <libcamera/base/log.h>
>  #include <libcamera/base/utils.h>
>  
> +#include "libcamera/internal/global_configuration.h"
>  #include "libcamera/internal/ipa_module.h"
>  
>  /**
> @@ -48,9 +51,12 @@ LOG_DEFINE_CATEGORY(IPAProxy)
>  /**
>   * \brief Construct an IPAProxy instance
>   * \param[in] ipam The IPA module
> + * \param[in] configuration The global configuration
>   */
> -IPAProxy::IPAProxy(IPAModule *ipam)
> -       : valid_(false), state_(ProxyStopped), ipam_(ipam)
> +IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
> +       : valid_(false), state_(ProxyStopped), ipam_(ipam),
> +         configPaths_(configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", { "ipa", "config_paths" }).value_or(std::vector<std::string>())),
> +         execPaths_(configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" }).value_or(std::vector<std::string>()))

Oh that's cool that we can do that now.

The rest looks good!


Thanks,

Paul

>  {
>  }
>  
> @@ -122,20 +128,15 @@ std::string IPAProxy::configurationFile(const std::string &name,
>         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;
> -               }
> +       for (const auto &dir : configPaths_) {
> +               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();
> @@ -199,18 +200,14 @@ std::string IPAProxy::resolvePath(const std::string &file) 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. */
> +       for (const auto &dir : execPaths_) {
> +               if (dir.empty())
> +                       continue;
> +
> +               std::string proxyPath = dir + proxyFile;
> +               if (!access(proxyPath.c_str(), X_OK))
> +                       return proxyPath;
>         }
>  
>         /*
> 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..0f87e7976 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,8 +45,8 @@ namespace {{ns}} {
>  {% endfor %}
>  {%- endif %}
>  
> -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
> -       : IPAProxy(ipam), isolate_(isolate),
> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
> +       : IPAProxy(ipam, configuration), isolate_(isolate),
>           controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
>  {
>         LOG(IPAProxy, Debug)
> 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 %}
> -- 
> 2.50.1
>
Milan Zamazal Sept. 9, 2025, 1:06 p.m. UTC | #2
Hi Paul,

thank you for review.

Paul Elder <paul.elder@ideasonboard.com> writes:

> Hi Milan,
>
> Thanks for the patch.
>
> Quoting Milan Zamazal (2025-07-29 16:31:53)
>> This patch adds configuration options for environment variables used in
>> the IPA proxy.  Two utility functions configuration retrieval functions
>
> s/Two.*$/Two utility functions for retrieving lists of configuration values/

OK.

>> are added, to retrieve lists of values.
>
> s/,.*$/\./
>
> Personally I feel like the retrieval functions should just all be added in
> 3/12. Then in this patch we can just plumb it into the IPAs.

Some reviewers (not necessarily in libcamera) don't like adding such
stuff before it is used, I don't mind either way.  I can move them.

>> 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
>> 
>> LIBCAMERA_<IPA_NAME>_TUNING_FILE remains configurable only via the
>> environment variable; this is supposed to be used only for testing and
>> debugging and it's not clear what to do about IPA names like "rpi/vc4"
>> and "rpi/pisp" exactly.
>
> I thought it would be fine (or even desirable) to allow configuring the path to
> the tuning files in the configuration file? It does make sense to keep the
> environment variable though.

Well, this has been discussed in
https://lists.libcamera.org/pipermail/libcamera-devel/2025-June/051202.html,
where Barnabás said:

  I think `LIBCAMERA_${IPA}_TUNING_FILE` is mainly a testing feature
  because it overrides the tuning file "globally" for the given IPA. My
  first impression was that it is probably not that useful to have in a
  configuration file. And I still think that, so my preference is not
  including it in the configuration file. With that, feel free to decide

So I removed the options (less things to review :-P) but I can put them
back if they are useful.  The naming issue doesn't exist any more.

>> There are two ways to pass the configuration to the places where it is
>> needed: Either to pass it as an argument to the method calls that need
>> it, or to pass it to the class constructors and extract the needed
>> configuration from there.  This patch uses the second method as it is
>> less polluting the code.
>> 
>> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> ---
>>  .../libcamera/internal/global_configuration.h | 21 +++++++-
>>  include/libcamera/internal/ipa_manager.h      |  7 ++-
>>  include/libcamera/internal/ipa_proxy.h        |  8 ++-
>>  src/libcamera/camera_manager.cpp              |  2 +-
>>  src/libcamera/global_configuration.cpp        | 47 +++++++++++++++--
>>  src/libcamera/ipa_manager.cpp                 | 39 ++++++++------
>>  src/libcamera/ipa_proxy.cpp                   | 51 +++++++++----------
>>  .../module_ipa_proxy.cpp.tmpl                 |  4 +-
>>  .../module_ipa_proxy.h.tmpl                   |  2 +-
>>  9 files changed, 128 insertions(+), 53 deletions(-)
>> 
>> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
>> index 78bebff9f..3b500ee29 100644
>> --- a/include/libcamera/internal/global_configuration.h
>> +++ b/include/libcamera/internal/global_configuration.h
>> @@ -12,6 +12,8 @@
>>  #include <string>
>>  #include <string_view>
>>  
>> +#include <libcamera/base/utils.h>
>> +
>>  #include "libcamera/internal/yaml_parser.h"
>>  
>>  namespace libcamera {
>> @@ -25,11 +27,28 @@ public:
>>  
>>         unsigned int version() const;
>>         Configuration configuration() const;
>> -       std::optional<std::string> option(
>> +
>> +       template<typename T>
>> +       std::optional<T> option(
>> +               const std::initializer_list<std::string_view> confPath) const
>> +       {
>> +               const YamlObject *c = &configuration();
>> +               for (auto part : confPath) {
>> +                       c = &(*c)[part];
>> +                       if (!*c)
>> +                               return {};
>> +               }
>> +               return c->get<T>();
>> +       }
>> +
>> +       std::optional<std::vector<std::string>> listOption(
>>                 const std::initializer_list<std::string_view> confPath) const;
>>         std::optional<std::string> envOption(
>>                 const char *const envVariable,
>>                 const std::initializer_list<std::string_view> confPath) const;
>> +       std::optional<std::vector<std::string>> envListOption(
>> +               const char *const envVariable,
>> +               const std::initializer_list<std::string_view> 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..b0b44c74a 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);
>
> Ok so we pass it in during construction and everybody that needs it keeps a
> pointer to it, that looks good.
>
>>                 if (!proxy->isValid()) {
>>                         LOG(IPAManager, Error) << "Failed to load proxy";
>>                         return nullptr;
>> @@ -73,6 +75,7 @@ private:
>>  #if HAVE_IPA_PUBKEY
>>         static const uint8_t publicKeyData_[];
>>         static const PubKey pubKey_;
>> +       bool forceIsolation_;
>>  #endif
>>  };
>>  
>> diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
>> index 983bcc5fa..f1865d67e 100644
>> --- a/include/libcamera/internal/ipa_proxy.h
>> +++ b/include/libcamera/internal/ipa_proxy.h
>> @@ -7,10 +7,14 @@
>>  
>>  #pragma once
>>  
>> +#include <optional>
>>  #include <string>
>> +#include <vector>
>>  
>>  #include <libcamera/ipa/ipa_interface.h>
>>  
>> +#include "libcamera/internal/global_configuration.h"
>> +
>>  namespace libcamera {
>>  
>>  class IPAModule;
>> @@ -24,7 +28,7 @@ public:
>>                 ProxyRunning,
>>         };
>>  
>> -       IPAProxy(IPAModule *ipam);
>> +       IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
>>         ~IPAProxy();
>>  
>>         bool isValid() const { return valid_; }
>> @@ -40,6 +44,8 @@ protected:
>>  
>>  private:
>>         IPAModule *ipam_;
>> +       std::vector<std::string> configPaths_;
>> +       std::vector<std::string> execPaths_;
>>  };
>>  
>>  } /* namespace libcamera */
>> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
>> index dca3d9a83..64df62444 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 5be0f0701..1371392c5 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>
>> @@ -114,13 +116,22 @@ GlobalConfiguration::GlobalConfiguration()
>>   */
>>  
>>  /**
>> + * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &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
>>   * \return A value if an item corresponding to \a confPath exists in the
>>   * configuration file, no value otherwise
>>   */
>> -std::optional<std::string> GlobalConfiguration::option(
>> +
>> +/**
>> + * \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 or no value if not found
>> + */
>> +std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
>>         const std::initializer_list<std::string_view> confPath) const
>>  {
>>         const YamlObject *c = &configuration();
>> @@ -129,7 +140,7 @@ std::optional<std::string> GlobalConfiguration::option(
>>                 if (!*c)
>>                         return {};
>>         }
>> -       return c->get<std::string>();
>> +       return c->getList<std::string>();
>>  }
>>  
>>  /**
>> @@ -156,7 +167,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.
>
> Same comments from 3/12.

The same responses :-).

>> + *
>> + * \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 or no value if not found; the vector may be empty
>> + */
>> +std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
>> +       const char *const envVariable,
>> +       const std::initializer_list<std::string_view> 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..000d56efa 100644
>> --- a/src/libcamera/ipa_manager.cpp
>> +++ b/src/libcamera/ipa_manager.cpp
>> @@ -9,13 +9,17 @@
>>  
>>  #include <algorithm>
>>  #include <dirent.h>
>> +#include <numeric>
>>  #include <string.h>
>> +#include <string>
>>  #include <sys/types.h>
>> +#include <vector>
>>  
>>  #include <libcamera/base/file.h>
>>  #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,30 +105,36 @@ 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())
>>                 LOG(IPAManager, Warning) << "Public key not valid";
>> +
>> +       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
>> +       forceIsolation_ = (force && force[0] != '\0') ||
>> +                         (!force && configuration.option<bool>({ "ipa", "force_isolation" })
>> +                                            .value_or(false));
>
> Although the environment variable doesn't let us do it, I wonder if there's
> value in letting the configuration file configure which IPAs we want to
> force-isolate and which we don't.

I don't have any opinion on this.

> Also is there a reason why we don't use envOption() here? It is because we
> can't get a bool out of env?

Yes, IIRC I didn't want to introduce another helper for just a single use.

>>  #endif
>>  
>>         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;
>> -
>> -                       ipaCount += addDir(dir.c_str());
>> -               }
>> +       const auto modulePaths =
>> +               configuration.envListOption(
>> +                       "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
>> +       for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
>> +               if (dir.empty())
>> +                       continue;
>>  
>> -               if (!ipaCount)
>> -                       LOG(IPAManager, Warning)
>> -                               << "No IPA found in '" << modulePaths << "'";
>> +               ipaCount += addDir(dir.c_str());
>>         }
>>  
>> +       if (!ipaCount)
>> +               LOG(IPAManager, Warning) << "No IPA found in '"
>> +                                        << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
>> +                                        << "'";
>> +
>
> Ok, looks good.
>
>>         /*
>>          * When libcamera is used before it is installed, load IPAs from the
>>          * same build directory as the libcamera library itself.
>> @@ -279,11 +289,10 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
>>  bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
>>  {
>>  #if HAVE_IPA_PUBKEY
>> -       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
>> -       if (force && force[0] != '\0') {
>> +       if (forceIsolation_) {
>>                 LOG(IPAManager, Debug)
>>                         << "Isolation of IPA module " << ipa->path()
>> -                       << " forced through environment variable";
>> +                       << " forced through configuration";
>>                 return false;
>>         }
>>  
>> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
>> index 9907b9615..b845e9086 100644
>> --- a/src/libcamera/ipa_proxy.cpp
>> +++ b/src/libcamera/ipa_proxy.cpp
>> @@ -7,13 +7,16 @@
>>  
>>  #include "libcamera/internal/ipa_proxy.h"
>>  
>> +#include <string>
>>  #include <sys/stat.h>
>>  #include <sys/types.h>
>>  #include <unistd.h>
>> +#include <vector>
>>  
>>  #include <libcamera/base/log.h>
>>  #include <libcamera/base/utils.h>
>>  
>> +#include "libcamera/internal/global_configuration.h"
>>  #include "libcamera/internal/ipa_module.h"
>>  
>>  /**
>> @@ -48,9 +51,12 @@ LOG_DEFINE_CATEGORY(IPAProxy)
>>  /**
>>   * \brief Construct an IPAProxy instance
>>   * \param[in] ipam The IPA module
>> + * \param[in] configuration The global configuration
>>   */
>> -IPAProxy::IPAProxy(IPAModule *ipam)
>> -       : valid_(false), state_(ProxyStopped), ipam_(ipam)
>> +IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
>> +       : valid_(false), state_(ProxyStopped), ipam_(ipam),
>> +         configPaths_(configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", { "ipa", "config_paths" }).value_or(std::vector<std::string>())),
>> +         execPaths_(configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" }).value_or(std::vector<std::string>()))
>
> Oh that's cool that we can do that now.
>
> The rest looks good!
>
>
> Thanks,
>
> Paul
>
>>  {
>>  }
>>  
>> @@ -122,20 +128,15 @@ std::string IPAProxy::configurationFile(const std::string &name,
>>         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;
>> -               }
>> +       for (const auto &dir : configPaths_) {
>> +               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();
>> @@ -199,18 +200,14 @@ std::string IPAProxy::resolvePath(const std::string &file) 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. */
>> +       for (const auto &dir : execPaths_) {
>> +               if (dir.empty())
>> +                       continue;
>> +
>> +               std::string proxyPath = dir + proxyFile;
>> +               if (!access(proxyPath.c_str(), X_OK))
>> +                       return proxyPath;
>>         }
>>  
>>         /*
>> 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..0f87e7976 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,8 +45,8 @@ namespace {{ns}} {
>>  {% endfor %}
>>  {%- endif %}
>>  
>> -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
>> -       : IPAProxy(ipam), isolate_(isolate),
>> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
>> +       : IPAProxy(ipam, configuration), isolate_(isolate),
>>           controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
>>  {
>>         LOG(IPAProxy, Debug)
>> 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 %}
>> -- 
>> 2.50.1
>>
Paul Elder Sept. 9, 2025, 2:38 p.m. UTC | #3
Quoting Milan Zamazal (2025-09-09 22:06:24)
> Hi Paul,
> 
> thank you for review.
> 
> Paul Elder <paul.elder@ideasonboard.com> writes:
> 
> > Hi Milan,
> >
> > Thanks for the patch.
> >
> > Quoting Milan Zamazal (2025-07-29 16:31:53)
> >> This patch adds configuration options for environment variables used in
> >> the IPA proxy.  Two utility functions configuration retrieval functions
> >
> > s/Two.*$/Two utility functions for retrieving lists of configuration values/
> 
> OK.
> 
> >> are added, to retrieve lists of values.
> >
> > s/,.*$/\./
> >
> > Personally I feel like the retrieval functions should just all be added in
> > 3/12. Then in this patch we can just plumb it into the IPAs.
> 
> Some reviewers (not necessarily in libcamera) don't like adding such
> stuff before it is used, I don't mind either way.  I can move them.

Oh I can imagine :S

imo it's fine to add them before it's used as long as it gets used later in the
same series (and it's easy to review).

> 
> >> 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
> >> 
> >> LIBCAMERA_<IPA_NAME>_TUNING_FILE remains configurable only via the
> >> environment variable; this is supposed to be used only for testing and
> >> debugging and it's not clear what to do about IPA names like "rpi/vc4"
> >> and "rpi/pisp" exactly.
> >
> > I thought it would be fine (or even desirable) to allow configuring the path to
> > the tuning files in the configuration file? It does make sense to keep the
> > environment variable though.
> 
> Well, this has been discussed in
> https://lists.libcamera.org/pipermail/libcamera-devel/2025-June/051202.html,

Thanks for the link.

> where Barnabás said:
> 
>   I think `LIBCAMERA_${IPA}_TUNING_FILE` is mainly a testing feature
>   because it overrides the tuning file "globally" for the given IPA. My
>   first impression was that it is probably not that useful to have in a
>   configuration file. And I still think that, so my preference is not
>   including it in the configuration file. With that, feel free to decide

Ah, I see.

> 
> So I removed the options (less things to review :-P) but I can put them
> back if they are useful.  The naming issue doesn't exist any more.

One of the big features that we want from the configuration file is actually to
be able to select the tuning file. However, I don't think directly
reimplementing LIBCAMERA_${IPA}_TUNING_FILE is the solution. So you can indeed
keep it out.

As for this feature that we want we can do it on top. I think it needs a bit
more refining how we define it that doesn't deserve to be a blocker to merge
this series.

> 
> >> There are two ways to pass the configuration to the places where it is
> >> needed: Either to pass it as an argument to the method calls that need
> >> it, or to pass it to the class constructors and extract the needed
> >> configuration from there.  This patch uses the second method as it is
> >> less polluting the code.
> >> 
> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> >> ---
> >>  .../libcamera/internal/global_configuration.h | 21 +++++++-
> >>  include/libcamera/internal/ipa_manager.h      |  7 ++-
> >>  include/libcamera/internal/ipa_proxy.h        |  8 ++-
> >>  src/libcamera/camera_manager.cpp              |  2 +-
> >>  src/libcamera/global_configuration.cpp        | 47 +++++++++++++++--
> >>  src/libcamera/ipa_manager.cpp                 | 39 ++++++++------
> >>  src/libcamera/ipa_proxy.cpp                   | 51 +++++++++----------
> >>  .../module_ipa_proxy.cpp.tmpl                 |  4 +-
> >>  .../module_ipa_proxy.h.tmpl                   |  2 +-
> >>  9 files changed, 128 insertions(+), 53 deletions(-)
> >> 
> >> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
> >> index 78bebff9f..3b500ee29 100644
> >> --- a/include/libcamera/internal/global_configuration.h
> >> +++ b/include/libcamera/internal/global_configuration.h
> >> @@ -12,6 +12,8 @@
> >>  #include <string>
> >>  #include <string_view>
> >>  
> >> +#include <libcamera/base/utils.h>
> >> +
> >>  #include "libcamera/internal/yaml_parser.h"
> >>  
> >>  namespace libcamera {
> >> @@ -25,11 +27,28 @@ public:
> >>  
> >>         unsigned int version() const;
> >>         Configuration configuration() const;
> >> -       std::optional<std::string> option(
> >> +
> >> +       template<typename T>
> >> +       std::optional<T> option(
> >> +               const std::initializer_list<std::string_view> confPath) const
> >> +       {
> >> +               const YamlObject *c = &configuration();
> >> +               for (auto part : confPath) {
> >> +                       c = &(*c)[part];
> >> +                       if (!*c)
> >> +                               return {};
> >> +               }
> >> +               return c->get<T>();
> >> +       }
> >> +
> >> +       std::optional<std::vector<std::string>> listOption(
> >>                 const std::initializer_list<std::string_view> confPath) const;
> >>         std::optional<std::string> envOption(
> >>                 const char *const envVariable,
> >>                 const std::initializer_list<std::string_view> confPath) const;
> >> +       std::optional<std::vector<std::string>> envListOption(
> >> +               const char *const envVariable,
> >> +               const std::initializer_list<std::string_view> 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..b0b44c74a 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);
> >
> > Ok so we pass it in during construction and everybody that needs it keeps a
> > pointer to it, that looks good.
> >
> >>                 if (!proxy->isValid()) {
> >>                         LOG(IPAManager, Error) << "Failed to load proxy";
> >>                         return nullptr;
> >> @@ -73,6 +75,7 @@ private:
> >>  #if HAVE_IPA_PUBKEY
> >>         static const uint8_t publicKeyData_[];
> >>         static const PubKey pubKey_;
> >> +       bool forceIsolation_;
> >>  #endif
> >>  };
> >>  
> >> diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
> >> index 983bcc5fa..f1865d67e 100644
> >> --- a/include/libcamera/internal/ipa_proxy.h
> >> +++ b/include/libcamera/internal/ipa_proxy.h
> >> @@ -7,10 +7,14 @@
> >>  
> >>  #pragma once
> >>  
> >> +#include <optional>
> >>  #include <string>
> >> +#include <vector>
> >>  
> >>  #include <libcamera/ipa/ipa_interface.h>
> >>  
> >> +#include "libcamera/internal/global_configuration.h"
> >> +
> >>  namespace libcamera {
> >>  
> >>  class IPAModule;
> >> @@ -24,7 +28,7 @@ public:
> >>                 ProxyRunning,
> >>         };
> >>  
> >> -       IPAProxy(IPAModule *ipam);
> >> +       IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
> >>         ~IPAProxy();
> >>  
> >>         bool isValid() const { return valid_; }
> >> @@ -40,6 +44,8 @@ protected:
> >>  
> >>  private:
> >>         IPAModule *ipam_;
> >> +       std::vector<std::string> configPaths_;
> >> +       std::vector<std::string> execPaths_;
> >>  };
> >>  
> >>  } /* namespace libcamera */
> >> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> >> index dca3d9a83..64df62444 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 5be0f0701..1371392c5 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>
> >> @@ -114,13 +116,22 @@ GlobalConfiguration::GlobalConfiguration()
> >>   */
> >>  
> >>  /**
> >> + * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &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
> >>   * \return A value if an item corresponding to \a confPath exists in the
> >>   * configuration file, no value otherwise
> >>   */
> >> -std::optional<std::string> GlobalConfiguration::option(
> >> +
> >> +/**
> >> + * \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 or no value if not found
> >> + */
> >> +std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
> >>         const std::initializer_list<std::string_view> confPath) const
> >>  {
> >>         const YamlObject *c = &configuration();
> >> @@ -129,7 +140,7 @@ std::optional<std::string> GlobalConfiguration::option(
> >>                 if (!*c)
> >>                         return {};
> >>         }
> >> -       return c->get<std::string>();
> >> +       return c->getList<std::string>();
> >>  }
> >>  
> >>  /**
> >> @@ -156,7 +167,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.
> >
> > Same comments from 3/12.
> 
> The same responses :-).
> 
> >> + *
> >> + * \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 or no value if not found; the vector may be empty
> >> + */
> >> +std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
> >> +       const char *const envVariable,
> >> +       const std::initializer_list<std::string_view> 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..000d56efa 100644
> >> --- a/src/libcamera/ipa_manager.cpp
> >> +++ b/src/libcamera/ipa_manager.cpp
> >> @@ -9,13 +9,17 @@
> >>  
> >>  #include <algorithm>
> >>  #include <dirent.h>
> >> +#include <numeric>
> >>  #include <string.h>
> >> +#include <string>
> >>  #include <sys/types.h>
> >> +#include <vector>
> >>  
> >>  #include <libcamera/base/file.h>
> >>  #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,30 +105,36 @@ 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())
> >>                 LOG(IPAManager, Warning) << "Public key not valid";
> >> +
> >> +       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
> >> +       forceIsolation_ = (force && force[0] != '\0') ||
> >> +                         (!force && configuration.option<bool>({ "ipa", "force_isolation" })
> >> +                                            .value_or(false));
> >
> > Although the environment variable doesn't let us do it, I wonder if there's
> > value in letting the configuration file configure which IPAs we want to
> > force-isolate and which we don't.
> 
> I don't have any opinion on this.

Ok. I guess we can add this in the future if somebody wants it. This framework
is well-designed that it should be easy to add.

> 
> > Also is there a reason why we don't use envOption() here? It is because we
> > can't get a bool out of env?
> 
> Yes, IIRC I didn't want to introduce another helper for just a single use.

Ok, that's fine then.


Thanks,

Paul

> 
> >>  #endif
> >>  
> >>         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;
> >> -
> >> -                       ipaCount += addDir(dir.c_str());
> >> -               }
> >> +       const auto modulePaths =
> >> +               configuration.envListOption(
> >> +                       "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
> >> +       for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
> >> +               if (dir.empty())
> >> +                       continue;
> >>  
> >> -               if (!ipaCount)
> >> -                       LOG(IPAManager, Warning)
> >> -                               << "No IPA found in '" << modulePaths << "'";
> >> +               ipaCount += addDir(dir.c_str());
> >>         }
> >>  
> >> +       if (!ipaCount)
> >> +               LOG(IPAManager, Warning) << "No IPA found in '"
> >> +                                        << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
> >> +                                        << "'";
> >> +
> >
> > Ok, looks good.
> >
> >>         /*
> >>          * When libcamera is used before it is installed, load IPAs from the
> >>          * same build directory as the libcamera library itself.
> >> @@ -279,11 +289,10 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
> >>  bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
> >>  {
> >>  #if HAVE_IPA_PUBKEY
> >> -       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
> >> -       if (force && force[0] != '\0') {
> >> +       if (forceIsolation_) {
> >>                 LOG(IPAManager, Debug)
> >>                         << "Isolation of IPA module " << ipa->path()
> >> -                       << " forced through environment variable";
> >> +                       << " forced through configuration";
> >>                 return false;
> >>         }
> >>  
> >> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
> >> index 9907b9615..b845e9086 100644
> >> --- a/src/libcamera/ipa_proxy.cpp
> >> +++ b/src/libcamera/ipa_proxy.cpp
> >> @@ -7,13 +7,16 @@
> >>  
> >>  #include "libcamera/internal/ipa_proxy.h"
> >>  
> >> +#include <string>
> >>  #include <sys/stat.h>
> >>  #include <sys/types.h>
> >>  #include <unistd.h>
> >> +#include <vector>
> >>  
> >>  #include <libcamera/base/log.h>
> >>  #include <libcamera/base/utils.h>
> >>  
> >> +#include "libcamera/internal/global_configuration.h"
> >>  #include "libcamera/internal/ipa_module.h"
> >>  
> >>  /**
> >> @@ -48,9 +51,12 @@ LOG_DEFINE_CATEGORY(IPAProxy)
> >>  /**
> >>   * \brief Construct an IPAProxy instance
> >>   * \param[in] ipam The IPA module
> >> + * \param[in] configuration The global configuration
> >>   */
> >> -IPAProxy::IPAProxy(IPAModule *ipam)
> >> -       : valid_(false), state_(ProxyStopped), ipam_(ipam)
> >> +IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
> >> +       : valid_(false), state_(ProxyStopped), ipam_(ipam),
> >> +         configPaths_(configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", { "ipa", "config_paths" }).value_or(std::vector<std::string>())),
> >> +         execPaths_(configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" }).value_or(std::vector<std::string>()))
> >
> > Oh that's cool that we can do that now.
> >
> > The rest looks good!
> >
> >
> > Thanks,
> >
> > Paul
> >
> >>  {
> >>  }
> >>  
> >> @@ -122,20 +128,15 @@ std::string IPAProxy::configurationFile(const std::string &name,
> >>         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;
> >> -               }
> >> +       for (const auto &dir : configPaths_) {
> >> +               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();
> >> @@ -199,18 +200,14 @@ std::string IPAProxy::resolvePath(const std::string &file) 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. */
> >> +       for (const auto &dir : execPaths_) {
> >> +               if (dir.empty())
> >> +                       continue;
> >> +
> >> +               std::string proxyPath = dir + proxyFile;
> >> +               if (!access(proxyPath.c_str(), X_OK))
> >> +                       return proxyPath;
> >>         }
> >>  
> >>         /*
> >> 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..0f87e7976 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,8 +45,8 @@ namespace {{ns}} {
> >>  {% endfor %}
> >>  {%- endif %}
> >>  
> >> -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
> >> -       : IPAProxy(ipam), isolate_(isolate),
> >> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
> >> +       : IPAProxy(ipam, configuration), isolate_(isolate),
> >>           controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
> >>  {
> >>         LOG(IPAProxy, Debug)
> >> 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 %}
> >> -- 
> >> 2.50.1
> >>
>
Milan Zamazal Sept. 9, 2025, 3:16 p.m. UTC | #4
Paul Elder <paul.elder@ideasonboard.com> writes:

> Quoting Milan Zamazal (2025-09-09 22:06:24)
>> Hi Paul,
>> 
>
>> thank you for review.
>> 
>> Paul Elder <paul.elder@ideasonboard.com> writes:
>> 
>> > Hi Milan,
>> >
>> > Thanks for the patch.
>> >
>> > Quoting Milan Zamazal (2025-07-29 16:31:53)
>> >> This patch adds configuration options for environment variables used in
>> >> the IPA proxy.  Two utility functions configuration retrieval functions
>> >
>> > s/Two.*$/Two utility functions for retrieving lists of configuration values/
>> 
>> OK.
>> 
>> >> are added, to retrieve lists of values.
>> >
>> > s/,.*$/\./
>> >
>> > Personally I feel like the retrieval functions should just all be added in
>> > 3/12. Then in this patch we can just plumb it into the IPAs.
>> 
>> Some reviewers (not necessarily in libcamera) don't like adding such
>> stuff before it is used, I don't mind either way.  I can move them.
>
> Oh I can imagine :S
>
> imo it's fine to add them before it's used as long as it gets used later in the
> same series (and it's easy to review).
>
>> 
>> >> 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
>> >> 
>> >> LIBCAMERA_<IPA_NAME>_TUNING_FILE remains configurable only via the
>> >> environment variable; this is supposed to be used only for testing and
>> >> debugging and it's not clear what to do about IPA names like "rpi/vc4"
>> >> and "rpi/pisp" exactly.
>> >
>> > I thought it would be fine (or even desirable) to allow configuring the path to
>> > the tuning files in the configuration file? It does make sense to keep the
>> > environment variable though.
>> 
>> Well, this has been discussed in
>> https://lists.libcamera.org/pipermail/libcamera-devel/2025-June/051202.html,
>
> Thanks for the link.
>
>> where Barnabás said:
>> 
>>   I think `LIBCAMERA_${IPA}_TUNING_FILE` is mainly a testing feature
>>   because it overrides the tuning file "globally" for the given IPA. My
>>   first impression was that it is probably not that useful to have in a
>>   configuration file. And I still think that, so my preference is not
>>   including it in the configuration file. With that, feel free to decide
>
> Ah, I see.
>
>> 
>> So I removed the options (less things to review :-P) but I can put them
>> back if they are useful.  The naming issue doesn't exist any more.
>
> One of the big features that we want from the configuration file is actually to
> be able to select the tuning file. However, I don't think directly
> reimplementing LIBCAMERA_${IPA}_TUNING_FILE is the solution. So you can indeed
> keep it out.
>
> As for this feature that we want we can do it on top. I think it needs a bit
> more refining how we define it that doesn't deserve to be a blocker to merge
> this series.

Yes, considering how long this series sits in review and adjustments,
it's better to focus on what's necessary right now and leave the rest
for followup work.

>> 
>> >> There are two ways to pass the configuration to the places where it is
>> >> needed: Either to pass it as an argument to the method calls that need
>> >> it, or to pass it to the class constructors and extract the needed
>> >> configuration from there.  This patch uses the second method as it is
>> >> less polluting the code.
>> >> 
>> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
>> >> ---
>> >>  .../libcamera/internal/global_configuration.h | 21 +++++++-
>> >>  include/libcamera/internal/ipa_manager.h      |  7 ++-
>> >>  include/libcamera/internal/ipa_proxy.h        |  8 ++-
>> >>  src/libcamera/camera_manager.cpp              |  2 +-
>> >>  src/libcamera/global_configuration.cpp        | 47 +++++++++++++++--
>> >>  src/libcamera/ipa_manager.cpp                 | 39 ++++++++------
>> >>  src/libcamera/ipa_proxy.cpp                   | 51 +++++++++----------
>> >>  .../module_ipa_proxy.cpp.tmpl                 |  4 +-
>> >>  .../module_ipa_proxy.h.tmpl                   |  2 +-
>> >>  9 files changed, 128 insertions(+), 53 deletions(-)
>> >> 
>> >> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
>> >> index 78bebff9f..3b500ee29 100644
>> >> --- a/include/libcamera/internal/global_configuration.h
>> >> +++ b/include/libcamera/internal/global_configuration.h
>> >> @@ -12,6 +12,8 @@
>> >>  #include <string>
>> >>  #include <string_view>
>> >>  
>> >> +#include <libcamera/base/utils.h>
>> >> +
>> >>  #include "libcamera/internal/yaml_parser.h"
>> >>  
>> >>  namespace libcamera {
>> >> @@ -25,11 +27,28 @@ public:
>> >>  
>> >>         unsigned int version() const;
>> >>         Configuration configuration() const;
>> >> -       std::optional<std::string> option(
>> >> +
>> >> +       template<typename T>
>> >> +       std::optional<T> option(
>> >> +               const std::initializer_list<std::string_view> confPath) const
>> >> +       {
>> >> +               const YamlObject *c = &configuration();
>> >> +               for (auto part : confPath) {
>> >> +                       c = &(*c)[part];
>> >> +                       if (!*c)
>> >> +                               return {};
>> >> +               }
>> >> +               return c->get<T>();
>> >> +       }
>> >> +
>> >> +       std::optional<std::vector<std::string>> listOption(
>> >>                 const std::initializer_list<std::string_view> confPath) const;
>> >>         std::optional<std::string> envOption(
>> >>                 const char *const envVariable,
>> >>                 const std::initializer_list<std::string_view> confPath) const;
>> >> +       std::optional<std::vector<std::string>> envListOption(
>> >> +               const char *const envVariable,
>> >> +               const std::initializer_list<std::string_view> 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..b0b44c74a 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);
>> >
>> > Ok so we pass it in during construction and everybody that needs it keeps a
>> > pointer to it, that looks good.
>> >
>> >>                 if (!proxy->isValid()) {
>> >>                         LOG(IPAManager, Error) << "Failed to load proxy";
>> >>                         return nullptr;
>> >> @@ -73,6 +75,7 @@ private:
>> >>  #if HAVE_IPA_PUBKEY
>> >>         static const uint8_t publicKeyData_[];
>> >>         static const PubKey pubKey_;
>> >> +       bool forceIsolation_;
>> >>  #endif
>> >>  };
>> >>  
>> >> diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
>> >> index 983bcc5fa..f1865d67e 100644
>> >> --- a/include/libcamera/internal/ipa_proxy.h
>> >> +++ b/include/libcamera/internal/ipa_proxy.h
>> >> @@ -7,10 +7,14 @@
>> >>  
>> >>  #pragma once
>> >>  
>> >> +#include <optional>
>> >>  #include <string>
>> >> +#include <vector>
>> >>  
>> >>  #include <libcamera/ipa/ipa_interface.h>
>> >>  
>> >> +#include "libcamera/internal/global_configuration.h"
>> >> +
>> >>  namespace libcamera {
>> >>  
>> >>  class IPAModule;
>> >> @@ -24,7 +28,7 @@ public:
>> >>                 ProxyRunning,
>> >>         };
>> >>  
>> >> -       IPAProxy(IPAModule *ipam);
>> >> +       IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
>> >>         ~IPAProxy();
>> >>  
>> >>         bool isValid() const { return valid_; }
>> >> @@ -40,6 +44,8 @@ protected:
>> >>  
>> >>  private:
>> >>         IPAModule *ipam_;
>> >> +       std::vector<std::string> configPaths_;
>> >> +       std::vector<std::string> execPaths_;
>> >>  };
>> >>  
>> >>  } /* namespace libcamera */
>> >> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
>> >> index dca3d9a83..64df62444 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 5be0f0701..1371392c5 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>
>> >> @@ -114,13 +116,22 @@ GlobalConfiguration::GlobalConfiguration()
>> >>   */
>> >>  
>> >>  /**
>> >> + * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &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
>> >>   * \return A value if an item corresponding to \a confPath exists in the
>> >>   * configuration file, no value otherwise
>> >>   */
>> >> -std::optional<std::string> GlobalConfiguration::option(
>> >> +
>> >> +/**
>> >> + * \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 or no value if not found
>> >> + */
>> >> +std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
>> >>         const std::initializer_list<std::string_view> confPath) const
>> >>  {
>> >>         const YamlObject *c = &configuration();
>> >> @@ -129,7 +140,7 @@ std::optional<std::string> GlobalConfiguration::option(
>> >>                 if (!*c)
>> >>                         return {};
>> >>         }
>> >> -       return c->get<std::string>();
>> >> +       return c->getList<std::string>();
>> >>  }
>> >>  
>> >>  /**
>> >> @@ -156,7 +167,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.
>> >
>> > Same comments from 3/12.
>> 
>> The same responses :-).
>> 
>> >> + *
>> >> + * \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 or no value if not found; the vector may be empty
>> >> + */
>> >> +std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
>> >> +       const char *const envVariable,
>> >> +       const std::initializer_list<std::string_view> 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..000d56efa 100644
>> >> --- a/src/libcamera/ipa_manager.cpp
>> >> +++ b/src/libcamera/ipa_manager.cpp
>> >> @@ -9,13 +9,17 @@
>> >>  
>> >>  #include <algorithm>
>> >>  #include <dirent.h>
>> >> +#include <numeric>
>> >>  #include <string.h>
>> >> +#include <string>
>> >>  #include <sys/types.h>
>> >> +#include <vector>
>> >>  
>> >>  #include <libcamera/base/file.h>
>> >>  #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,30 +105,36 @@ 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())
>> >>                 LOG(IPAManager, Warning) << "Public key not valid";
>> >> +
>> >> +       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
>> >> +       forceIsolation_ = (force && force[0] != '\0') ||
>> >> +                         (!force && configuration.option<bool>({ "ipa", "force_isolation" })
>> >> +                                            .value_or(false));
>> >
>> > Although the environment variable doesn't let us do it, I wonder if there's
>> > value in letting the configuration file configure which IPAs we want to
>> > force-isolate and which we don't.
>> 
>> I don't have any opinion on this.
>
> Ok. I guess we can add this in the future if somebody wants it. This framework
> is well-designed that it should be easy to add.
>
>> 
>> > Also is there a reason why we don't use envOption() here? It is because we
>> > can't get a bool out of env?
>> 
>> Yes, IIRC I didn't want to introduce another helper for just a single use.
>
> Ok, that's fine then.
>
>
> Thanks,
>
> Paul
>
>> 
>> >>  #endif
>> >>  
>> >>         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;
>> >> -
>> >> -                       ipaCount += addDir(dir.c_str());
>> >> -               }
>> >> +       const auto modulePaths =
>> >> +               configuration.envListOption(
>> >> +                       "LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
>> >> +       for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
>> >> +               if (dir.empty())
>> >> +                       continue;
>> >>  
>> >> -               if (!ipaCount)
>> >> -                       LOG(IPAManager, Warning)
>> >> -                               << "No IPA found in '" << modulePaths << "'";
>> >> +               ipaCount += addDir(dir.c_str());
>> >>         }
>> >>  
>> >> +       if (!ipaCount)
>> >> +               LOG(IPAManager, Warning) << "No IPA found in '"
>> >> +                                        << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
>> >> +                                        << "'";
>> >> +
>> >
>> > Ok, looks good.
>> >
>> >>         /*
>> >>          * When libcamera is used before it is installed, load IPAs from the
>> >>          * same build directory as the libcamera library itself.
>> >> @@ -279,11 +289,10 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
>> >>  bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
>> >>  {
>> >>  #if HAVE_IPA_PUBKEY
>> >> -       char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
>> >> -       if (force && force[0] != '\0') {
>> >> +       if (forceIsolation_) {
>> >>                 LOG(IPAManager, Debug)
>> >>                         << "Isolation of IPA module " << ipa->path()
>> >> -                       << " forced through environment variable";
>> >> +                       << " forced through configuration";
>> >>                 return false;
>> >>         }
>> >>  
>> >> diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
>> >> index 9907b9615..b845e9086 100644
>> >> --- a/src/libcamera/ipa_proxy.cpp
>> >> +++ b/src/libcamera/ipa_proxy.cpp
>> >> @@ -7,13 +7,16 @@
>> >>  
>> >>  #include "libcamera/internal/ipa_proxy.h"
>> >>  
>> >> +#include <string>
>> >>  #include <sys/stat.h>
>> >>  #include <sys/types.h>
>> >>  #include <unistd.h>
>> >> +#include <vector>
>> >>  
>> >>  #include <libcamera/base/log.h>
>> >>  #include <libcamera/base/utils.h>
>> >>  
>> >> +#include "libcamera/internal/global_configuration.h"
>> >>  #include "libcamera/internal/ipa_module.h"
>> >>  
>> >>  /**
>> >> @@ -48,9 +51,12 @@ LOG_DEFINE_CATEGORY(IPAProxy)
>> >>  /**
>> >>   * \brief Construct an IPAProxy instance
>> >>   * \param[in] ipam The IPA module
>> >> + * \param[in] configuration The global configuration
>> >>   */
>> >> -IPAProxy::IPAProxy(IPAModule *ipam)
>> >> -       : valid_(false), state_(ProxyStopped), ipam_(ipam)
>> >> +IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
>> >> +       : valid_(false), state_(ProxyStopped), ipam_(ipam),
>> >> +         configPaths_(configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", { "ipa", "config_paths" }).value_or(std::vector<std::string>())),
>> >> +         execPaths_(configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" }).value_or(std::vector<std::string>()))
>> >
>> > Oh that's cool that we can do that now.
>> >
>> > The rest looks good!
>> >
>> >
>> > Thanks,
>> >
>> > Paul
>> >
>> >>  {
>> >>  }
>> >>  
>> >> @@ -122,20 +128,15 @@ std::string IPAProxy::configurationFile(const std::string &name,
>> >>         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;
>> >> -               }
>> >> +       for (const auto &dir : configPaths_) {
>> >> +               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();
>> >> @@ -199,18 +200,14 @@ std::string IPAProxy::resolvePath(const std::string &file) 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. */
>> >> +       for (const auto &dir : execPaths_) {
>> >> +               if (dir.empty())
>> >> +                       continue;
>> >> +
>> >> +               std::string proxyPath = dir + proxyFile;
>> >> +               if (!access(proxyPath.c_str(), X_OK))
>> >> +                       return proxyPath;
>> >>         }
>> >>  
>> >>         /*
>> >> 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..0f87e7976 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,8 +45,8 @@ namespace {{ns}} {
>> >>  {% endfor %}
>> >>  {%- endif %}
>> >>  
>> >> -{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
>> >> -       : IPAProxy(ipam), isolate_(isolate),
>> >> +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
>> >> +       : IPAProxy(ipam, configuration), isolate_(isolate),
>> >>           controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
>> >>  {
>> >>         LOG(IPAProxy, Debug)
>> >> 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 %}
>> >> -- 
>> >> 2.50.1
>> >>
>>

Patch
diff mbox series

diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 78bebff9f..3b500ee29 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -12,6 +12,8 @@ 
 #include <string>
 #include <string_view>
 
+#include <libcamera/base/utils.h>
+
 #include "libcamera/internal/yaml_parser.h"
 
 namespace libcamera {
@@ -25,11 +27,28 @@  public:
 
 	unsigned int version() const;
 	Configuration configuration() const;
-	std::optional<std::string> option(
+
+	template<typename T>
+	std::optional<T> option(
+		const std::initializer_list<std::string_view> confPath) const
+	{
+		const YamlObject *c = &configuration();
+		for (auto part : confPath) {
+			c = &(*c)[part];
+			if (!*c)
+				return {};
+		}
+		return c->get<T>();
+	}
+
+	std::optional<std::vector<std::string>> listOption(
 		const std::initializer_list<std::string_view> confPath) const;
 	std::optional<std::string> envOption(
 		const char *const envVariable,
 		const std::initializer_list<std::string_view> confPath) const;
+	std::optional<std::vector<std::string>> envListOption(
+		const char *const envVariable,
+		const std::initializer_list<std::string_view> 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..b0b44c74a 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);
 		if (!proxy->isValid()) {
 			LOG(IPAManager, Error) << "Failed to load proxy";
 			return nullptr;
@@ -73,6 +75,7 @@  private:
 #if HAVE_IPA_PUBKEY
 	static const uint8_t publicKeyData_[];
 	static const PubKey pubKey_;
+	bool forceIsolation_;
 #endif
 };
 
diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
index 983bcc5fa..f1865d67e 100644
--- a/include/libcamera/internal/ipa_proxy.h
+++ b/include/libcamera/internal/ipa_proxy.h
@@ -7,10 +7,14 @@ 
 
 #pragma once
 
+#include <optional>
 #include <string>
+#include <vector>
 
 #include <libcamera/ipa/ipa_interface.h>
 
+#include "libcamera/internal/global_configuration.h"
+
 namespace libcamera {
 
 class IPAModule;
@@ -24,7 +28,7 @@  public:
 		ProxyRunning,
 	};
 
-	IPAProxy(IPAModule *ipam);
+	IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
 	~IPAProxy();
 
 	bool isValid() const { return valid_; }
@@ -40,6 +44,8 @@  protected:
 
 private:
 	IPAModule *ipam_;
+	std::vector<std::string> configPaths_;
+	std::vector<std::string> execPaths_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index dca3d9a83..64df62444 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 5be0f0701..1371392c5 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>
@@ -114,13 +116,22 @@  GlobalConfiguration::GlobalConfiguration()
  */
 
 /**
+ * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &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
  * \return A value if an item corresponding to \a confPath exists in the
  * configuration file, no value otherwise
  */
-std::optional<std::string> GlobalConfiguration::option(
+
+/**
+ * \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 or no value if not found
+ */
+std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
 	const std::initializer_list<std::string_view> confPath) const
 {
 	const YamlObject *c = &configuration();
@@ -129,7 +140,7 @@  std::optional<std::string> GlobalConfiguration::option(
 		if (!*c)
 			return {};
 	}
-	return c->get<std::string>();
+	return c->getList<std::string>();
 }
 
 /**
@@ -156,7 +167,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 or no value if not found; the vector may be empty
+ */
+std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
+	const char *const envVariable,
+	const std::initializer_list<std::string_view> 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..000d56efa 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -9,13 +9,17 @@ 
 
 #include <algorithm>
 #include <dirent.h>
+#include <numeric>
 #include <string.h>
+#include <string>
 #include <sys/types.h>
+#include <vector>
 
 #include <libcamera/base/file.h>
 #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,30 +105,36 @@  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())
 		LOG(IPAManager, Warning) << "Public key not valid";
+
+	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
+	forceIsolation_ = (force && force[0] != '\0') ||
+			  (!force && configuration.option<bool>({ "ipa", "force_isolation" })
+					     .value_or(false));
 #endif
 
 	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;
-
-			ipaCount += addDir(dir.c_str());
-		}
+	const auto modulePaths =
+		configuration.envListOption(
+			"LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
+	for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
+		if (dir.empty())
+			continue;
 
-		if (!ipaCount)
-			LOG(IPAManager, Warning)
-				<< "No IPA found in '" << modulePaths << "'";
+		ipaCount += addDir(dir.c_str());
 	}
 
+	if (!ipaCount)
+		LOG(IPAManager, Warning) << "No IPA found in '"
+					 << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
+					 << "'";
+
 	/*
 	 * When libcamera is used before it is installed, load IPAs from the
 	 * same build directory as the libcamera library itself.
@@ -279,11 +289,10 @@  IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
 bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
 {
 #if HAVE_IPA_PUBKEY
-	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
-	if (force && force[0] != '\0') {
+	if (forceIsolation_) {
 		LOG(IPAManager, Debug)
 			<< "Isolation of IPA module " << ipa->path()
-			<< " forced through environment variable";
+			<< " forced through configuration";
 		return false;
 	}
 
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 9907b9615..b845e9086 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -7,13 +7,16 @@ 
 
 #include "libcamera/internal/ipa_proxy.h"
 
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <vector>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 
 /**
@@ -48,9 +51,12 @@  LOG_DEFINE_CATEGORY(IPAProxy)
 /**
  * \brief Construct an IPAProxy instance
  * \param[in] ipam The IPA module
+ * \param[in] configuration The global configuration
  */
-IPAProxy::IPAProxy(IPAModule *ipam)
-	: valid_(false), state_(ProxyStopped), ipam_(ipam)
+IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
+	: valid_(false), state_(ProxyStopped), ipam_(ipam),
+	  configPaths_(configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH", { "ipa", "config_paths" }).value_or(std::vector<std::string>())),
+	  execPaths_(configuration.envListOption("LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" }).value_or(std::vector<std::string>()))
 {
 }
 
@@ -122,20 +128,15 @@  std::string IPAProxy::configurationFile(const std::string &name,
 	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;
-		}
+	for (const auto &dir : configPaths_) {
+		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();
@@ -199,18 +200,14 @@  std::string IPAProxy::resolvePath(const std::string &file) 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. */
+	for (const auto &dir : execPaths_) {
+		if (dir.empty())
+			continue;
+
+		std::string proxyPath = dir + proxyFile;
+		if (!access(proxyPath.c_str(), X_OK))
+			return proxyPath;
 	}
 
 	/*
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..0f87e7976 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,8 +45,8 @@  namespace {{ns}} {
 {% endfor %}
 {%- endif %}
 
-{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
-	: IPAProxy(ipam), isolate_(isolate),
+{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
+	: IPAProxy(ipam, configuration), isolate_(isolate),
 	  controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
 {
 	LOG(IPAProxy, Debug)
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 %}