Express the ownership more clearly by using a smart pointer type. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
311 lines
9.1 KiB
C++
311 lines
9.1 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2019, Google Inc.
|
|
*
|
|
* Image Processing Algorithm module manager
|
|
*/
|
|
|
|
#include "libcamera/internal/ipa_manager.h"
|
|
|
|
#include <algorithm>
|
|
#include <dirent.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <libcamera/base/file.h>
|
|
#include <libcamera/base/log.h>
|
|
#include <libcamera/base/utils.h>
|
|
|
|
#include "libcamera/internal/ipa_module.h"
|
|
#include "libcamera/internal/ipa_proxy.h"
|
|
#include "libcamera/internal/pipeline_handler.h"
|
|
|
|
/**
|
|
* \file ipa_manager.h
|
|
* \brief Image Processing Algorithm module manager
|
|
*/
|
|
|
|
namespace libcamera {
|
|
|
|
LOG_DEFINE_CATEGORY(IPAManager)
|
|
|
|
/**
|
|
* \class IPAManager
|
|
* \brief Manager for IPA modules
|
|
*
|
|
* The IPA module manager discovers IPA modules from disk, queries and loads
|
|
* them, and creates IPA contexts. It supports isolation of the modules in a
|
|
* separate process with IPC communication and offers a unified IPAInterface
|
|
* view of the IPA contexts to pipeline handlers regardless of whether the
|
|
* modules are isolated or loaded in the same process.
|
|
*
|
|
* Module isolation is based on the module licence. Open-source modules are
|
|
* loaded without isolation, while closed-source module are forcefully isolated.
|
|
* The isolation mechanism ensures that no code from a closed-source module is
|
|
* ever run in the libcamera process.
|
|
*
|
|
* To create an IPA context, pipeline handlers call the IPAManager::createIPA()
|
|
* function. For a directly loaded module, the manager calls the module's
|
|
* ipaCreate() function directly and wraps the returned context in an
|
|
* IPAContextWrapper that exposes an IPAInterface.
|
|
*
|
|
* ~~~~
|
|
* +---------------+
|
|
* | Pipeline |
|
|
* | Handler |
|
|
* +---------------+
|
|
* |
|
|
* v
|
|
* +---------------+ +---------------+
|
|
* | IPA | | Open Source |
|
|
* | Interface | | IPA Module |
|
|
* | - - - - - - - | | - - - - - - - |
|
|
* | IPA Context | ipa_context_ops | ipa_context |
|
|
* | Wrapper | ----------------> | |
|
|
* +---------------+ +---------------+
|
|
* ~~~~
|
|
*
|
|
* For an isolated module, the manager instantiates an IPAProxy which spawns a
|
|
* new process for an IPA proxy worker. The worker loads the IPA module and
|
|
* creates the IPA context. The IPAProxy alse exposes an IPAInterface.
|
|
*
|
|
* ~~~~
|
|
* +---------------+ +---------------+
|
|
* | Pipeline | | Closed Source |
|
|
* | Handler | | IPA Module |
|
|
* +---------------+ | - - - - - - - |
|
|
* | | ipa_context |
|
|
* v | |
|
|
* +---------------+ +---------------+
|
|
* | IPA | ipa_context_ops ^
|
|
* | Interface | |
|
|
* | - - - - - - - | +---------------+
|
|
* | IPA Proxy | operations | IPA Proxy |
|
|
* | | ----------------> | Worker |
|
|
* +---------------+ over IPC +---------------+
|
|
* ~~~~
|
|
*
|
|
* The IPAInterface implemented by the IPAContextWrapper or IPAProxy is
|
|
* returned to the pipeline handler, and all interactions with the IPA context
|
|
* go the same interface regardless of process isolation.
|
|
*
|
|
* In all cases the data passed to the IPAInterface member functions is
|
|
* serialized to Plain Old Data, either for the purpose of passing it to the IPA
|
|
* context plain C API, or to transmit the data to the isolated process through
|
|
* IPC.
|
|
*/
|
|
|
|
/**
|
|
* \brief Construct an IPAManager instance
|
|
*
|
|
* The IPAManager class is meant to only be instantiated once, by the
|
|
* CameraManager.
|
|
*/
|
|
IPAManager::IPAManager()
|
|
{
|
|
#if HAVE_IPA_PUBKEY
|
|
if (!pubKey_.isValid())
|
|
LOG(IPAManager, Warning) << "Public key not valid";
|
|
#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());
|
|
}
|
|
|
|
if (!ipaCount)
|
|
LOG(IPAManager, Warning)
|
|
<< "No IPA found in '" << modulePaths << "'";
|
|
}
|
|
|
|
/*
|
|
* When libcamera is used before it is installed, load IPAs from the
|
|
* same build directory as the libcamera library itself.
|
|
*/
|
|
std::string root = utils::libcameraBuildPath();
|
|
if (!root.empty()) {
|
|
std::string ipaBuildPath = root + "src/ipa";
|
|
constexpr int maxDepth = 2;
|
|
|
|
LOG(IPAManager, Info)
|
|
<< "libcamera is not installed. Adding '"
|
|
<< ipaBuildPath << "' to the IPA search path";
|
|
|
|
ipaCount += addDir(ipaBuildPath.c_str(), maxDepth);
|
|
}
|
|
|
|
/* Finally try to load IPAs from the installed system path. */
|
|
ipaCount += addDir(IPA_MODULE_DIR);
|
|
|
|
if (!ipaCount)
|
|
LOG(IPAManager, Warning)
|
|
<< "No IPA found in '" IPA_MODULE_DIR "'";
|
|
}
|
|
|
|
IPAManager::~IPAManager() = default;
|
|
|
|
/**
|
|
* \brief Identify shared library objects within a directory
|
|
* \param[in] libDir The directory to search for shared objects
|
|
* \param[in] maxDepth The maximum depth of sub-directories to parse
|
|
* \param[out] files A vector of paths to shared object library files
|
|
*
|
|
* Search a directory for .so files, allowing recursion down to sub-directories
|
|
* no further than the depth specified by \a maxDepth.
|
|
*
|
|
* Discovered shared objects are added to the \a files vector.
|
|
*/
|
|
void IPAManager::parseDir(const char *libDir, unsigned int maxDepth,
|
|
std::vector<std::string> &files)
|
|
{
|
|
struct dirent *ent;
|
|
DIR *dir;
|
|
|
|
dir = opendir(libDir);
|
|
if (!dir)
|
|
return;
|
|
|
|
while ((ent = readdir(dir)) != nullptr) {
|
|
if (ent->d_type == DT_DIR && maxDepth) {
|
|
if (strcmp(ent->d_name, ".") == 0 ||
|
|
strcmp(ent->d_name, "..") == 0)
|
|
continue;
|
|
|
|
std::string subdir = std::string(libDir) + "/" + ent->d_name;
|
|
|
|
/* Recursion is limited to maxDepth. */
|
|
parseDir(subdir.c_str(), maxDepth - 1, files);
|
|
|
|
continue;
|
|
}
|
|
|
|
int offset = strlen(ent->d_name) - 3;
|
|
if (offset < 0)
|
|
continue;
|
|
if (strcmp(&ent->d_name[offset], ".so"))
|
|
continue;
|
|
|
|
files.push_back(std::string(libDir) + "/" + ent->d_name);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
/**
|
|
* \brief Load IPA modules from a directory
|
|
* \param[in] libDir The directory to search for IPA modules
|
|
* \param[in] maxDepth The maximum depth of sub-directories to search
|
|
*
|
|
* This function tries to create an IPAModule instance for every shared object
|
|
* found in \a libDir, and skips invalid IPA modules.
|
|
*
|
|
* Sub-directories are searched up to a depth of \a maxDepth. A \a maxDepth
|
|
* value of 0 only searches the directory specified in \a libDir.
|
|
*
|
|
* \return Number of modules loaded by this call
|
|
*/
|
|
unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth)
|
|
{
|
|
std::vector<std::string> files;
|
|
|
|
parseDir(libDir, maxDepth, files);
|
|
|
|
/* Ensure a stable ordering of modules. */
|
|
std::sort(files.begin(), files.end());
|
|
|
|
unsigned int count = 0;
|
|
for (const std::string &file : files) {
|
|
auto ipaModule = std::make_unique<IPAModule>(file);
|
|
if (!ipaModule->isValid())
|
|
continue;
|
|
|
|
LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'";
|
|
|
|
modules_.push_back(std::move(ipaModule));
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve an IPA module that matches a given pipeline handler
|
|
* \param[in] pipe The pipeline handler
|
|
* \param[in] minVersion Minimum acceptable version of IPA module
|
|
* \param[in] maxVersion Maximum acceptable version of IPA module
|
|
*/
|
|
IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
|
|
uint32_t maxVersion)
|
|
{
|
|
for (const auto &module : modules_) {
|
|
if (module->match(pipe, minVersion, maxVersion))
|
|
return module.get();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* \fn IPAManager::createIPA()
|
|
* \brief Create an IPA proxy that matches a given pipeline handler
|
|
* \param[in] pipe The pipeline handler that wants a matching IPA proxy
|
|
* \param[in] minVersion Minimum acceptable version of IPA module
|
|
* \param[in] maxVersion Maximum acceptable version of IPA module
|
|
*
|
|
* \return A newly created IPA proxy, or nullptr if no matching IPA module is
|
|
* found or if the IPA proxy fails to initialize
|
|
*/
|
|
|
|
#if HAVE_IPA_PUBKEY
|
|
/**
|
|
* \fn IPAManager::pubKey()
|
|
* \brief Retrieve the IPA module signing public key
|
|
*
|
|
* IPA module signature verification is normally handled internally by the
|
|
* IPAManager class. This function is meant to be used by utilities that need to
|
|
* verify signatures externally.
|
|
*
|
|
* \return The IPA module signing public key
|
|
*/
|
|
#endif
|
|
|
|
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') {
|
|
LOG(IPAManager, Debug)
|
|
<< "Isolation of IPA module " << ipa->path()
|
|
<< " forced through environment variable";
|
|
return false;
|
|
}
|
|
|
|
File file{ ipa->path() };
|
|
if (!file.open(File::OpenModeFlag::ReadOnly))
|
|
return false;
|
|
|
|
Span<uint8_t> data = file.map();
|
|
if (data.empty())
|
|
return false;
|
|
|
|
bool valid = pubKey_.verify(data, ipa->signature());
|
|
|
|
LOG(IPAManager, Debug)
|
|
<< "IPA module " << ipa->path() << " signature is "
|
|
<< (valid ? "valid" : "not valid");
|
|
|
|
return valid;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
} /* namespace libcamera */
|