@@ -793,6 +793,7 @@ WARN_LOGFILE =
INPUT = "@TOP_SRCDIR@/include/ipa" \
"@TOP_SRCDIR@/include/libcamera" \
+ "@TOP_SRCDIR@/src/ipa/libipa" \
"@TOP_SRCDIR@/src/libcamera"
# This tag can be used to specify the character encoding of the source files
@@ -24,6 +24,8 @@ if doxygen.found()
libcamera_ipa_api,
libcamera_headers,
libcamera_sources,
+ libipa_headers,
+ libipa_sources,
],
output : 'api-html',
command : [doxygen, doxyfile],
@@ -7,6 +7,21 @@
#ifndef __LIBCAMERA_IPA_INTERFACE_H__
#define __LIBCAMERA_IPA_INTERFACE_H__
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ipa_context {
+ const struct ipa_operations *ops;
+};
+
+struct ipa_operations {
+ void (*destroy)(struct ipa_context *ctx);
+};
+
+#ifdef __cplusplus
+}
+
namespace libcamera {
class IPAInterface
@@ -16,5 +31,6 @@ public:
};
} /* namespace libcamera */
+#endif
#endif /* __LIBCAMERA_IPA_INTERFACE_H__ */
@@ -8,6 +8,8 @@
#include <ipa/ipa_interface.h>
#include <ipa/ipa_module_info.h>
+#include "libipa/ipa_interface_wrapper.h"
+
namespace libcamera {
class IPADummy : public IPAInterface
@@ -27,9 +29,9 @@ const struct IPAModuleInfo ipaModuleInfo = {
LICENSE,
};
-IPAInterface *ipaCreate()
+struct ipa_context *ipaCreate()
{
- return new IPADummy();
+ return new IPAInterfaceWrapper(new IPADummy());
}
};
new file mode 100644
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_interface_wrapper.cpp - Image Processing Algorithm interface wrapper
+ */
+
+#include "ipa_interface_wrapper.h"
+
+#include <ipa/ipa_interface.h>
+
+/**
+ * \file ipa_interface_wrapper.h
+ * \brief Image Processing Algorithm interface wrapper
+ */
+
+namespace libcamera {
+
+/**
+ * \class IPAInterfaceWrapper
+ * \brief Wrap an IPAInterface and expose it as an ipa_context
+ *
+ * This class implements the ipa_context API based on a provided IPAInterface.
+ * It helps IPAs that implement the IPAInterface API to provide the external
+ * ipa_context API.
+ *
+ * To use the wrapper, an IPA module simple creates a new instance of its
+ * IPAInterface implementation, and passes it to the constructor of the
+ * IPAInterfaceWrapper. As IPAInterfaceWrapper inherits from ipa_context, the
+ * constructed wrapper can then be directly returned from the IPA module's
+ * ipaCreate() function.
+ *
+ * \code{.cpp}
+ * class MyIPA : public IPAInterface
+ * {
+ * ...
+ * };
+ *
+ * struct ipa_context *ipaCreate()
+ * {
+ * return new IPAInterfaceWrapper(new MyIPA());
+ * }
+ * \endcode
+ */
+
+/**
+ * \brief Construct an IPAInterfaceWrapper wrapping \a interface
+ * \param[in] interface The interface to wrap
+ */
+IPAInterfaceWrapper::IPAInterfaceWrapper(IPAInterface *interface)
+ : ipa(interface)
+{
+ ops = &operations;
+}
+
+void IPAInterfaceWrapper::destroy(struct ipa_context *_ctx)
+{
+ IPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);
+
+ delete ctx->ipa;
+ delete ctx;
+}
+
+#ifndef __DOXYGEN__
+/*
+ * This construct confuses Doygen and makes it believe that all members of the
+ * operations is a member of IPAInterfaceWrapper. It must thus be hidden.
+ */
+const struct ipa_operations IPAInterfaceWrapper::operations = {
+ .destroy = &IPAInterfaceWrapper::destroy,
+};
+#endif
+
+}; /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_interface_wrapper.h - Image Processing Algorithm interface wrapper
+ */
+#ifndef __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__
+#define __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__
+
+#include <ipa/ipa_interface.h>
+
+namespace libcamera {
+
+class IPAInterfaceWrapper : public ipa_context
+{
+public:
+ IPAInterfaceWrapper(IPAInterface *interface);
+
+private:
+ static void destroy(struct ipa_context *ctx);
+
+ static const struct ipa_operations operations;
+
+ IPAInterface *ipa;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__ */
new file mode 100644
@@ -0,0 +1,10 @@
+libipa_headers = files([
+ 'ipa_interface_wrapper.h',
+])
+
+libipa_sources = files([
+ 'ipa_interface_wrapper.cpp',
+])
+
+libipa = static_library('ipa', libipa_sources,
+ dependencies : libcamera_dep)
@@ -1,3 +1,5 @@
+subdir('libipa')
+
ipa_dummy_sources = [
['ipa_dummy', 'LGPL-2.1-or-later'],
['ipa_dummy_isolate', 'Proprietary'],
@@ -9,6 +11,7 @@ foreach t : ipa_dummy_sources
ipa = shared_module(t[0], 'ipa_dummy.cpp',
name_prefix : '',
include_directories : libcamera_includes,
+ link_with : libipa,
install : true,
install_dir : ipa_install_dir,
cpp_args : '-DLICENSE="' + t[1] + '"')
new file mode 100644
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_context_wrapper.h - Image Processing Algorithm context wrapper
+ */
+#ifndef __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__
+#define __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__
+
+#include <ipa/ipa_interface.h>
+
+namespace libcamera {
+
+class IPAContextWrapper final : public IPAInterface
+{
+public:
+ IPAContextWrapper(struct ipa_context *context);
+ ~IPAContextWrapper();
+
+private:
+ struct ipa_context *ctx_;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__ */
@@ -7,7 +7,6 @@
#ifndef __LIBCAMERA_IPA_MODULE_H__
#define __LIBCAMERA_IPA_MODULE_H__
-#include <memory>
#include <string>
#include <ipa/ipa_interface.h>
@@ -30,7 +29,7 @@ public:
bool load();
- std::unique_ptr<IPAInterface> createInstance();
+ struct ipa_context *createContext();
bool match(PipelineHandler *pipe,
uint32_t minVersion, uint32_t maxVersion) const;
@@ -45,7 +44,7 @@ private:
bool loaded_;
void *dlHandle_;
- typedef IPAInterface *(*IPAIntfFactory)(void);
+ typedef struct ipa_context *(*IPAIntfFactory)(void);
IPAIntfFactory ipaCreate_;
int loadIPAModuleInfo();
@@ -5,6 +5,7 @@ libcamera_headers = files([
'device_enumerator_udev.h',
'event_dispatcher_poll.h',
'formats.h',
+ 'ipa_context_wrapper.h',
'ipa_manager.h',
'ipa_module.h',
'ipa_proxy.h',
new file mode 100644
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_context_wrapper.cpp - Image Processing Algorithm context wrapper
+ */
+
+#include "ipa_context_wrapper.h"
+
+#include <libcamera/controls.h>
+
+/**
+ * \file ipa_context_wrapper.h
+ * \brief Image Processing Algorithm context wrapper
+ */
+
+namespace libcamera {
+
+/**
+ * \class IPAContextWrapper
+ * \brief Wrap an ipa_context and expose it as an IPAInterface
+ *
+ * The IPAContextWrapper class wraps an ipa_context, provided by an IPA module, and
+ * exposes an IPAInterface. This mechanism is used for IPAs that are not
+ * isolated in a separate process to allow direct calls from pipeline handler
+ * using the IPAInterface API instead of the lower-level ipa_context API.
+ *
+ * The IPAInterface methods are converted to the ipa_context API by serialising
+ * all C++ arguments into plain C structures or byte arrays that contain no
+ * pointer, as required by the ipa_context API.
+ */
+
+/**
+ * \brief Construct an IPAContextWrapper instance that wraps the \a context
+ * \param[in] context The IPA module context
+ *
+ * Ownership of the \a context is passed to the IPAContextWrapper. The context remains
+ * valid for the whole lifetime of the wrapper and is destroyed automatically
+ * with it.
+ */
+IPAContextWrapper::IPAContextWrapper(struct ipa_context *context)
+ : ctx_(context)
+{
+}
+
+IPAContextWrapper::~IPAContextWrapper()
+{
+ if (ctx_)
+ ctx_->ops->destroy(ctx_);
+}
+
+} /* namespace libcamera */
@@ -10,13 +10,88 @@
/**
* \file ipa_interface.h
* \brief Image Processing Algorithm interface
+ *
+ * libcamera interfaces with IPA modules through a plain C interface. IPA
+ * modules shall expose a public function name ipaCreate() with the following
+ * prototype.
+ *
+ * \code{.c}
+ * struct ipa_context *ipaCreate();
+ * \endcode
+ *
+ * The ipaCreate() function creates an instance of an IPA context, which models
+ * a context of execution for the IPA. IPA modules shall support creating one
+ * or multiple contexts, as required by their associated pipeline handler.
+ *
+ * The IPA module operations are defined in the struct ipa_operations. An IPA
+ * module stores a pointer to the operations corresponding to its context in
+ * the ipa_context::ops field. That pointer is immutable for the lifetime of
+ * the context, and may be different for multiple contexts created by the same
+ * IPA module.
+ *
+ * All argument to ipa_operations members are Plain Old Data and are documented
+ * either in the form of C data types, or as a textual description for byte
+ * arrays that can't be expressed using C data types (such as variable-length
+ * arrays). IPA modules can thus use the C API without calling into libcamera
+ * to access the data passed to the IPA operations.
+ *
+ * The IPAInterface class is a C++ representation of the ipa_operations, using
+ * C++ data classes provided by libcamera. This is the API exposed to pipeline
+ * handlers to communicate with IPA modules. IPA modules may use the
+ * IPAInterface API internally if they want to benefit from the data and helper
+ * classes offered by libcamera.
+ */
+
+/**
+ * \struct ipa_context
+ * \brief IPA module context of execution
+ *
+ * This structure models a context of execution for an IPA module. It is
+ * instantiated by the IPA module ipaCreate() function. IPA modules allocate
+ * context instances in an implementation-defined way, contexts shall thus be
+ * destroyed using the ipa_operation::destroy operation only.
+ *
+ * The ipa_context structure provides a pointer to the IPA operations. It shall
+ * otherwise be treated as a constant black-box cookie and passed unmodified to
+ * the operations defined in struct ipa_operations.
+ *
+ * IPA modules are expected to extend struct ipa_context by inheriting from it,
+ * either through structure embedding to model inheritance in plain C, or
+ * through C++ class inheritance. A simple example of the latter is available
+ * in the IPAContextWrapper class implementation.
+ *
+ * \var ipa_context::ops
+ * \brief The IPA context operations
+ */
+
+/**
+ * \struct ipa_operations
+ * \brief IPA context operations as a set of function pointers
+ *
+ * To allow for isolation of IPA modules in separate processes, the functions
+ * defined in the ipa_operations structure return only data related to the
+ * libcamera side of the operations. In particular, error related to the
+ * libcamera side of the IPC may be returned. Data returned by the IPA,
+ * including status information, shall be provided through callbacks from the
+ * IPA to libcamera.
+ *
+ * \var ipa_operations::destroy
+ * \brief Destroy the ipa_context created by the module's ipaCreate() function
*/
namespace libcamera {
/**
* \class IPAInterface
- * \brief Interface for IPA implementation
+ * \brief IPA module interface
+ *
+ * This pure virtual class defines a C++ API corresponding to the ipa_context
+ * and ipa_operations API. It is used by pipeline handlers to interact with IPA
+ * modules, and may be used internally in IPA modules if desired to benefit
+ * from the data and helper classes provided by libcamera.
+ *
+ * As for the operations defined in struct ipa_operations, the methods defined
+ * by this class shall not return data from the IPA.
*/
} /* namespace libcamera */
@@ -11,6 +11,7 @@
#include <string.h>
#include <sys/types.h>
+#include "ipa_context_wrapper.h"
#include "ipa_module.h"
#include "ipa_proxy.h"
#include "log.h"
@@ -29,6 +30,66 @@ 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::ipaCreate()
+ * method. 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_operations | 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_operations ^
+ * | 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 regarless of process isolation.
+ *
+ * In all cases the data passed to the IPAInterface methods is serialised 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.
*/
IPAManager::IPAManager()
@@ -189,7 +250,11 @@ std::unique_ptr<IPAInterface> IPAManager::createIPA(PipelineHandler *pipe,
if (!m->load())
return nullptr;
- return m->createInstance();
+ struct ipa_context *ctx = m->createContext();
+ if (!ctx)
+ return nullptr;
+
+ return utils::make_unique<IPAContextWrapper>(ctx);
}
} /* namespace libcamera */
@@ -385,13 +385,13 @@ const std::string &IPAModule::path() const
/**
* \brief Load the IPA implementation factory from the shared object
*
- * The IPA module shared object implements an IPAInterface class to be used
+ * The IPA module shared object implements an ipa_context object to be used
* by pipeline handlers. This method loads the factory function from the
- * shared object. Later, createInstance() can be called to instantiate the
- * IPAInterface.
+ * shared object. Later, createContext() can be called to instantiate the
+ * ipa_context.
*
* This method only needs to be called successfully once, after which
- * createInstance() can be called as many times as IPAInterface instances are
+ * createContext() can be called as many times as ipa_context instances are
* needed.
*
* Calling this function on an invalid module (as returned by isValid()) is
@@ -433,24 +433,25 @@ bool IPAModule::load()
}
/**
- * \brief Instantiate an IPAInterface
+ * \brief Instantiate an IPA context
*
- * After loading the IPA module with load(), this method creates an
- * instance of the IPA module interface.
+ * After loading the IPA module with load(), this method creates an instance of
+ * the IPA module context. Ownership of the context is passed to the caller, and
+ * the context shall be destroyed by calling the \ref ipa_operations::destroy
+ * "ipa_context::ops::destroy()" operation.
*
* Calling this function on a module that has not yet been loaded, or an
* invalid module (as returned by load() and isValid(), respectively) is
* an error.
*
- * \return The IPA implementation as a new IPAInterface instance on success,
- * or nullptr on error
+ * \return The IPA context on success, or nullptr on error
*/
-std::unique_ptr<IPAInterface> IPAModule::createInstance()
+struct ipa_context *IPAModule::createContext()
{
if (!valid_ || !loaded_)
return nullptr;
- return std::unique_ptr<IPAInterface>(ipaCreate_());
+ return ipaCreate_();
}
/**
@@ -13,6 +13,7 @@ libcamera_sources = files([
'event_notifier.cpp',
'formats.cpp',
'geometry.cpp',
+ 'ipa_context_wrapper.cpp',
'ipa_interface.cpp',
'ipa_manager.cpp',
'ipa_module.cpp',
@@ -362,7 +362,7 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
return false;
ipa_ = IPAManager::instance()->createIPA(this, 0, 0);
- if (ipa_ == nullptr)
+ if (!ipa_)
LOG(VIMC, Warning) << "no matching IPA found";
std::unique_ptr<VimcCameraData> data = utils::make_unique<VimcCameraData>(this);
@@ -72,9 +72,9 @@ int main(int argc, char **argv)
}
socket.readyRead.connect(&readyRead);
- std::unique_ptr<IPAInterface> ipa = ipam->createInstance();
- if (!ipa) {
- LOG(IPAProxyLinuxWorker, Error) << "Failed to create IPA interface";
+ struct ipa_context *ipac = ipam->createContext();
+ if (!ipac) {
+ LOG(IPAProxyLinuxWorker, Error) << "Failed to create IPA context";
return EXIT_FAILURE;
}
@@ -85,5 +85,7 @@ int main(int argc, char **argv)
while (1)
dispatcher->processEvents();
+ ipac->ops->destroy(ipac);
+
return 0;
}