Show a patch.

GET /api/patches/2316/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2316,
    "url": "https://patchwork.libcamera.org/api/patches/2316/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/2316/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20191108205409.18845-21-laurent.pinchart@ideasonboard.com>",
    "date": "2019-11-08T20:54:05",
    "name": "[libcamera-devel,v2,20/24] ipa: Switch to the plain C API",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "6058e9ae3d0a3d761f7158d99740feeab24e0d59",
    "submitter": {
        "id": 2,
        "url": "https://patchwork.libcamera.org/api/people/2/?format=api",
        "name": "Laurent Pinchart",
        "email": "laurent.pinchart@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/2316/mbox/",
    "series": [
        {
            "id": 568,
            "url": "https://patchwork.libcamera.org/api/series/568/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=568",
            "date": "2019-11-08T20:53:45",
            "name": "Control serialization and IPA C API",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/568/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/2316/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/2316/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<laurent.pinchart@ideasonboard.com>",
        "Received": [
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A126960180\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Nov 2019 21:54:29 +0100 (CET)",
            "from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 2413531D;\n\tFri,  8 Nov 2019 21:54:29 +0100 (CET)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1573246469;\n\tbh=H0SKLf2heJNBdl0GFtmEBtFSiH6ZwIjZw70rdL5BO5E=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=reOUUU0VNSoUQ5O+DMh22RKkpL+FVIiSw7/LIy6pu2bLdgsA7DUb5VtAKwq6f+XK7\n\ttMa3C1mFrWbv6IA+tqDyDISkIqDeI3l522SzfCK2e57lqlgg3W75Gcv2f75xctt7cS\n\tIRHtfGabIjycxDac0HZe4z/uLzoxKTtAHikBB3Dw=",
        "From": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Fri,  8 Nov 2019 22:54:05 +0200",
        "Message-Id": "<20191108205409.18845-21-laurent.pinchart@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.23.0",
        "In-Reply-To": "<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>",
        "References": "<20191108205409.18845-1-laurent.pinchart@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v2 20/24] ipa: Switch to the plain C API",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "X-List-Received-Date": "Fri, 08 Nov 2019 20:54:29 -0000"
    },
    "content": "From: Jacopo Mondi <jacopo@jmondi.org>\n\nSwitch IPA communication to the plain C API. As the IPAInterface class\nis easier to use for pipeline handlers than a plain C API, retain it and\nadd an IPAContextWrapper that translate between the C++ and the C APIs.\n\nOn the IPA module side usage of IPAInterface may be desired for IPAs\nimplemented in C++ that want to link to libcamera. For those IPAs, a new\nIPAInterfaceWrapper helper class is introduced to wrap the IPAInterface\nimplemented internally by the IPA module into an ipa_context,\nipa_context_ops and ipa_callback_ops.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n Documentation/Doxyfile.in                     |   1 +\n Documentation/meson.build                     |   2 +\n src/ipa/ipa_vimc.cpp                          |   7 +-\n src/ipa/libipa/ipa_interface_wrapper.cpp      | 240 ++++++++++++++++++\n src/ipa/libipa/ipa_interface_wrapper.h        |  56 ++++\n src/ipa/libipa/meson.build                    |  13 +\n src/ipa/meson.build                           |   3 +\n src/ipa/rkisp1/meson.build                    |   3 +-\n src/ipa/rkisp1/rkisp1.cpp                     |   5 +-\n src/libcamera/include/ipa_context_wrapper.h   |  43 ++++\n src/libcamera/include/ipa_module.h            |   5 +-\n src/libcamera/include/meson.build             |   1 +\n src/libcamera/ipa_context_wrapper.cpp         | 219 ++++++++++++++++\n src/libcamera/ipa_manager.cpp                 |  67 ++++-\n src/libcamera/ipa_module.cpp                  |  23 +-\n src/libcamera/meson.build                     |   1 +\n .../proxy/worker/ipa_proxy_linux_worker.cpp   |   8 +-\n 17 files changed, 674 insertions(+), 23 deletions(-)\n create mode 100644 src/ipa/libipa/ipa_interface_wrapper.cpp\n create mode 100644 src/ipa/libipa/ipa_interface_wrapper.h\n create mode 100644 src/ipa/libipa/meson.build\n create mode 100644 src/libcamera/include/ipa_context_wrapper.h\n create mode 100644 src/libcamera/ipa_context_wrapper.cpp",
    "diff": "diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\nindex 24babfd8b366..840c1b4c76c5 100644\n--- a/Documentation/Doxyfile.in\n+++ b/Documentation/Doxyfile.in\n@@ -793,6 +793,7 @@ WARN_LOGFILE           =\n \n INPUT                  = \"@TOP_SRCDIR@/include/ipa\" \\\n \t\t\t \"@TOP_SRCDIR@/include/libcamera\" \\\n+\t\t\t \"@TOP_SRCDIR@/src/ipa/libipa\" \\\n \t\t\t \"@TOP_SRCDIR@/src/libcamera\" \\\n \t\t\t \"@TOP_BUILDDIR@/include/libcamera\" \\\n \t\t\t \"@TOP_BUILDDIR@/src/libcamera\"\ndiff --git a/Documentation/meson.build b/Documentation/meson.build\nindex 4ff3fbeb0674..9136506f5d9c 100644\n--- a/Documentation/meson.build\n+++ b/Documentation/meson.build\n@@ -24,6 +24,8 @@ if doxygen.found()\n                       libcamera_ipa_api,\n                       libcamera_headers,\n                       libcamera_sources,\n+                      libipa_headers,\n+                      libipa_sources,\n                   ],\n                   output : 'api-html',\n                   command : [doxygen, doxyfile],\ndiff --git a/src/ipa/ipa_vimc.cpp b/src/ipa/ipa_vimc.cpp\nindex 50ca8dd805fb..8f03e811acc7 100644\n--- a/src/ipa/ipa_vimc.cpp\n+++ b/src/ipa/ipa_vimc.cpp\n@@ -17,7 +17,10 @@\n #include <ipa/ipa_interface.h>\n #include <ipa/ipa_module_info.h>\n \n+#include \"libipa/ipa_interface_wrapper.h\"\n+\n #include \"log.h\"\n+#include \"utils.h\"\n \n namespace libcamera {\n \n@@ -108,9 +111,9 @@ const struct IPAModuleInfo ipaModuleInfo = {\n \tLICENSE,\n };\n \n-IPAInterface *ipaCreate()\n+struct ipa_context *ipaCreate()\n {\n-\treturn new IPAVimc();\n+\treturn new IPAInterfaceWrapper(utils::make_unique<IPAVimc>());\n }\n }\n \ndiff --git a/src/ipa/libipa/ipa_interface_wrapper.cpp b/src/ipa/libipa/ipa_interface_wrapper.cpp\nnew file mode 100644\nindex 000000000000..80c5648ffed6\n--- /dev/null\n+++ b/src/ipa/libipa/ipa_interface_wrapper.cpp\n@@ -0,0 +1,240 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * ipa_interface_wrapper.cpp - Image Processing Algorithm interface wrapper\n+ */\n+\n+#include \"ipa_interface_wrapper.h\"\n+\n+#include <map>\n+#include <string.h>\n+#include <unistd.h>\n+#include <vector>\n+\n+#include <ipa/ipa_interface.h>\n+\n+#include \"byte_stream_buffer.h\"\n+\n+/**\n+ * \\file ipa_interface_wrapper.h\n+ * \\brief Image Processing Algorithm interface wrapper\n+ */\n+\n+namespace libcamera {\n+\n+/**\n+ * \\class IPAInterfaceWrapper\n+ * \\brief Wrap an IPAInterface and expose it as an ipa_context\n+ *\n+ * This class implements the ipa_context API based on a provided IPAInterface.\n+ * It helps IPAs that implement the IPAInterface API to provide the external\n+ * ipa_context API.\n+ *\n+ * To use the wrapper, an IPA module simple creates a new instance of its\n+ * IPAInterface implementation, and passes it to the constructor of the\n+ * IPAInterfaceWrapper. As IPAInterfaceWrapper inherits from ipa_context, the\n+ * constructed wrapper can then be directly returned from the IPA module's\n+ * ipaCreate() function.\n+ *\n+ * \\code{.cpp}\n+ * class MyIPA : public IPAInterface\n+ * {\n+ * \t...\n+ * };\n+ *\n+ * struct ipa_context *ipaCreate()\n+ * {\n+ * \treturn new IPAInterfaceWrapper(utils::make_unique<MyIPA>());\n+ * }\n+ * \\endcode\n+ *\n+ * The wrapper takes ownership of the IPAInterface and will automatically\n+ * delete it when the wrapper is destroyed.\n+ */\n+\n+/**\n+ * \\brief Construct an IPAInterfaceWrapper wrapping \\a interface\n+ * \\param[in] interface The interface to wrap\n+ */\n+IPAInterfaceWrapper::IPAInterfaceWrapper(std::unique_ptr<IPAInterface> interface)\n+\t: ipa_(std::move(interface)), callbacks_(nullptr), cb_ctx_(nullptr)\n+{\n+\tops = &operations_;\n+\n+\tipa_->queueFrameAction.connect(this, &IPAInterfaceWrapper::queueFrameAction);\n+}\n+\n+void IPAInterfaceWrapper::destroy(struct ipa_context *_ctx)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\n+\tdelete ctx;\n+}\n+\n+void IPAInterfaceWrapper::init(struct ipa_context *_ctx)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\n+\tctx->ipa_->init();\n+}\n+\n+void IPAInterfaceWrapper::register_callbacks(struct ipa_context *_ctx,\n+\t\t\t\t\t     const struct ipa_callback_ops *callbacks,\n+\t\t\t\t\t     void *cb_ctx)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\n+\tctx->callbacks_ = callbacks;\n+\tctx->cb_ctx_ = cb_ctx;\n+}\n+\n+void IPAInterfaceWrapper::configure(struct ipa_context *_ctx,\n+\t\t\t\t    const struct ipa_stream *streams,\n+\t\t\t\t    unsigned int num_streams,\n+\t\t\t\t    const struct ipa_control_info_map *maps,\n+\t\t\t\t    unsigned int num_maps)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\n+\tctx->serializer_.reset();\n+\n+\t/* Translate the IPA stream configurations map. */\n+\tstd::map<unsigned int, IPAStream> ipaStreams;\n+\n+\tfor (unsigned int i = 0; i < num_streams; ++i) {\n+\t\tconst struct ipa_stream &stream = streams[i];\n+\n+\t\tipaStreams[stream.id] = {\n+\t\t\tstream.pixel_format,\n+\t\t\tSize(stream.width, stream.height),\n+\t\t};\n+\t}\n+\n+\t/* Translate the IPA entity controls map. */\n+\tstd::map<unsigned int, const ControlInfoMap &> entityControls;\n+\tstd::map<unsigned int, ControlInfoMap> infoMaps;\n+\n+\tfor (unsigned int i = 0; i < num_maps; ++i) {\n+\t\tconst struct ipa_control_info_map &ipa_map = maps[i];\n+\t\tByteStreamBuffer byteStream(ipa_map.data, ipa_map.size);\n+\t\tunsigned int id = ipa_map.id;\n+\n+\t\tinfoMaps[id] = ctx->serializer_.deserialize<ControlInfoMap>(byteStream);\n+\t\tentityControls.emplace(id, infoMaps[id]);\n+\t}\n+\n+\tctx->ipa_->configure(ipaStreams, entityControls);\n+}\n+\n+void IPAInterfaceWrapper::map_buffers(struct ipa_context *_ctx,\n+\t\t\t\t      const struct ipa_buffer *_buffers,\n+\t\t\t\t      size_t num_buffers)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\tstd::vector<IPABuffer> buffers(num_buffers);\n+\n+\tfor (unsigned int i = 0; i < num_buffers; ++i) {\n+\t\tconst struct ipa_buffer &_buffer = _buffers[i];\n+\t\tIPABuffer &buffer = buffers[i];\n+\t\tstd::vector<Plane> &planes = buffer.memory.planes();\n+\n+\t\tbuffer.id = _buffer.id;\n+\n+\t\tplanes.resize(_buffer.num_planes);\n+\t\tfor (unsigned int j = 0; j < _buffer.num_planes; ++j) {\n+\t\t\tif (_buffer.planes[j].dmabuf != -1)\n+\t\t\t\tplanes[j].setDmabuf(_buffer.planes[j].dmabuf,\n+\t\t\t\t\t\t    _buffer.planes[j].length);\n+\t\t\t/** \\todo Create a Dmabuf class to implement RAII. */\n+\t\t\t::close(_buffer.planes[j].dmabuf);\n+\t\t}\n+\t}\n+\n+\tctx->ipa_->mapBuffers(buffers);\n+}\n+\n+void IPAInterfaceWrapper::unmap_buffers(struct ipa_context *_ctx,\n+\t\t\t\t\tconst unsigned int *_ids,\n+\t\t\t\t\tsize_t num_buffers)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\tstd::vector<unsigned int> ids(_ids, _ids + num_buffers);\n+\tctx->ipa_->unmapBuffers(ids);\n+}\n+\n+void IPAInterfaceWrapper::process_event(struct ipa_context *_ctx,\n+\t\t\t\t\tconst struct ipa_operation_data *data)\n+{\n+\tIPAInterfaceWrapper *ctx = static_cast<IPAInterfaceWrapper *>(_ctx);\n+\tIPAOperationData opData;\n+\n+\topData.operation = data->operation;\n+\n+\topData.data.resize(data->num_data);\n+\tmemcpy(opData.data.data(), data->data,\n+\t       data->num_data * sizeof(*data->data));\n+\n+\topData.controls.resize(data->num_lists);\n+\tfor (unsigned int i = 0; i < data->num_lists; ++i) {\n+\t\tconst struct ipa_control_list *c_list = &data->lists[i];\n+\t\tByteStreamBuffer byteStream(c_list->data, c_list->size);\n+\t\topData.controls[i] = ctx->serializer_.deserialize<ControlList>(byteStream);\n+\t}\n+\n+\tctx->ipa_->processEvent(opData);\n+}\n+\n+void IPAInterfaceWrapper::queueFrameAction(unsigned int frame,\n+\t\t\t\t\t   const IPAOperationData &data)\n+{\n+\tif (!callbacks_)\n+\t\treturn;\n+\n+\tstruct ipa_operation_data c_data;\n+\tc_data.operation = data.operation;\n+\tc_data.data = data.data.data();\n+\tc_data.num_data = data.data.size();\n+\n+\tstruct ipa_control_list control_lists[data.controls.size()];\n+\tc_data.lists = control_lists;\n+\tc_data.num_lists = data.controls.size();\n+\n+\tstd::size_t listsSize = 0;\n+\tfor (const auto &list : data.controls)\n+\t\tlistsSize += serializer_.binarySize(list);\n+\n+\tstd::vector<uint8_t> binaryData(listsSize);\n+\tByteStreamBuffer byteStreamBuffer(binaryData.data(), listsSize);\n+\n+\tunsigned int i = 0;\n+\tfor (const auto &list : data.controls) {\n+\t\tstruct ipa_control_list &c_list = control_lists[i];\n+\t\tc_list.size = serializer_.binarySize(list);\n+\n+\t\tByteStreamBuffer b = byteStreamBuffer.carveOut(c_list.size);\n+\t\tserializer_.serialize(list, b);\n+\n+\t\tc_list.data = b.base();\n+\t}\n+\n+\tcallbacks_->queue_frame_action(cb_ctx_, frame, c_data);\n+}\n+\n+#ifndef __DOXYGEN__\n+/*\n+ * This construct confuses Doygen and makes it believe that all members of the\n+ * operations is a member of IPAInterfaceWrapper. It must thus be hidden.\n+ */\n+const struct ipa_context_ops IPAInterfaceWrapper::operations_ = {\n+\t.destroy = &IPAInterfaceWrapper::destroy,\n+\t.init = &IPAInterfaceWrapper::init,\n+\t.register_callbacks = &IPAInterfaceWrapper::register_callbacks,\n+\t.configure = &IPAInterfaceWrapper::configure,\n+\t.map_buffers = &IPAInterfaceWrapper::map_buffers,\n+\t.unmap_buffers = &IPAInterfaceWrapper::unmap_buffers,\n+\t.process_event = &IPAInterfaceWrapper::process_event,\n+};\n+#endif\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/ipa_interface_wrapper.h b/src/ipa/libipa/ipa_interface_wrapper.h\nnew file mode 100644\nindex 000000000000..17be2062b6c7\n--- /dev/null\n+++ b/src/ipa/libipa/ipa_interface_wrapper.h\n@@ -0,0 +1,56 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * ipa_interface_wrapper.h - Image Processing Algorithm interface wrapper\n+ */\n+#ifndef __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__\n+#define __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__\n+\n+#include <memory>\n+\n+#include <ipa/ipa_interface.h>\n+\n+#include \"control_serializer.h\"\n+\n+namespace libcamera {\n+\n+class IPAInterfaceWrapper : public ipa_context\n+{\n+public:\n+\tIPAInterfaceWrapper(std::unique_ptr<IPAInterface> interface);\n+\n+private:\n+\tstatic void destroy(struct ipa_context *ctx);\n+\tstatic void init(struct ipa_context *ctx);\n+\tstatic void register_callbacks(struct ipa_context *ctx,\n+\t\t\t\t       const struct ipa_callback_ops *callbacks,\n+\t\t\t\t       void *cb_ctx);\n+\tstatic void configure(struct ipa_context *ctx,\n+\t\t\t      const struct ipa_stream *streams,\n+\t\t\t      unsigned int num_streams,\n+\t\t\t      const struct ipa_control_info_map *maps,\n+\t\t\t      unsigned int num_maps);\n+\tstatic void map_buffers(struct ipa_context *ctx,\n+\t\t\t\tconst struct ipa_buffer *c_buffers,\n+\t\t\t\tsize_t num_buffers);\n+\tstatic void unmap_buffers(struct ipa_context *ctx,\n+\t\t\t\t  const unsigned int *ids,\n+\t\t\t\t  size_t num_buffers);\n+\tstatic void process_event(struct ipa_context *ctx,\n+\t\t\t\t  const struct ipa_operation_data *data);\n+\n+\tstatic const struct ipa_context_ops operations_;\n+\n+\tvoid queueFrameAction(unsigned int frame, const IPAOperationData &data);\n+\n+\tstd::unique_ptr<IPAInterface> ipa_;\n+\tconst struct ipa_callback_ops *callbacks_;\n+\tvoid *cb_ctx_;\n+\n+\tControlSerializer serializer_;\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__ */\ndiff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\nnew file mode 100644\nindex 000000000000..6f3cd4866ce3\n--- /dev/null\n+++ b/src/ipa/libipa/meson.build\n@@ -0,0 +1,13 @@\n+libipa_headers = files([\n+    'ipa_interface_wrapper.h',\n+])\n+\n+libipa_sources = files([\n+    'ipa_interface_wrapper.cpp',\n+])\n+\n+libipa_includes = include_directories('..')\n+\n+libipa = static_library('ipa', libipa_sources,\n+                        include_directories : ipa_includes,\n+                        dependencies : libcamera_dep)\ndiff --git a/src/ipa/meson.build b/src/ipa/meson.build\nindex 4f2a45771201..421803243e32 100644\n--- a/src/ipa/meson.build\n+++ b/src/ipa/meson.build\n@@ -10,11 +10,14 @@ ipa_includes = [\n     libcamera_internal_includes,\n ]\n \n+subdir('libipa')\n+\n foreach t : ipa_vimc_sources\n     ipa = shared_module(t[0], 'ipa_vimc.cpp',\n                         name_prefix : '',\n                         include_directories : ipa_includes,\n                         dependencies : libcamera_dep,\n+                        link_with : libipa,\n                         install : true,\n                         install_dir : ipa_install_dir,\n                         cpp_args : '-DLICENSE=\"' + t[1] + '\"')\ndiff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build\nindex 1cab319ce6be..521518bd1237 100644\n--- a/src/ipa/rkisp1/meson.build\n+++ b/src/ipa/rkisp1/meson.build\n@@ -1,7 +1,8 @@\n rkisp1_ipa = shared_module('ipa_rkisp1',\n                            'rkisp1.cpp',\n                            name_prefix : '',\n-                           include_directories : ipa_includes,\n+                           include_directories : [ipa_includes, libipa_includes],\n                            dependencies : libcamera_dep,\n+                           link_with : libipa,\n                            install : true,\n                            install_dir : ipa_install_dir)\ndiff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\nindex 41babf0c4140..744a16ae5b9a 100644\n--- a/src/ipa/rkisp1/rkisp1.cpp\n+++ b/src/ipa/rkisp1/rkisp1.cpp\n@@ -19,6 +19,7 @@\n #include <libcamera/buffer.h>\n #include <libcamera/control_ids.h>\n #include <libcamera/request.h>\n+#include <libipa/ipa_interface_wrapper.h>\n \n #include \"log.h\"\n #include \"utils.h\"\n@@ -247,9 +248,9 @@ const struct IPAModuleInfo ipaModuleInfo = {\n \t\"LGPL-2.1-or-later\",\n };\n \n-IPAInterface *ipaCreate()\n+struct ipa_context *ipaCreate()\n {\n-\treturn new IPARkISP1();\n+\treturn new IPAInterfaceWrapper(utils::make_unique<IPARkISP1>());\n }\n }\n \ndiff --git a/src/libcamera/include/ipa_context_wrapper.h b/src/libcamera/include/ipa_context_wrapper.h\nnew file mode 100644\nindex 000000000000..060888218838\n--- /dev/null\n+++ b/src/libcamera/include/ipa_context_wrapper.h\n@@ -0,0 +1,43 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * ipa_context_wrapper.h - Image Processing Algorithm context wrapper\n+ */\n+#ifndef __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__\n+#define __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__\n+\n+#include <ipa/ipa_interface.h>\n+\n+#include \"control_serializer.h\"\n+\n+namespace libcamera {\n+\n+class IPAContextWrapper final : public IPAInterface\n+{\n+public:\n+\tIPAContextWrapper(struct ipa_context *context);\n+\t~IPAContextWrapper();\n+\n+\tint init() override;\n+\tvoid configure(const std::map<unsigned int, IPAStream> &streamConfig,\n+\t\t       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override;\n+\n+\tvoid mapBuffers(const std::vector<IPABuffer> &buffers) override;\n+\tvoid unmapBuffers(const std::vector<unsigned int> &ids) override;\n+\n+\tvirtual void processEvent(const IPAOperationData &data) override;\n+\n+private:\n+\tstatic void queue_frame_action(void *ctx, unsigned int frame,\n+\t\t\t\t       struct ipa_operation_data &data);\n+\tstatic const struct ipa_callback_ops callbacks_;\n+\n+\tstruct ipa_context *ctx_;\n+\n+\tControlSerializer serializer_;\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__ */\ndiff --git a/src/libcamera/include/ipa_module.h b/src/libcamera/include/ipa_module.h\nindex 97737587ab3a..2028b76a1913 100644\n--- a/src/libcamera/include/ipa_module.h\n+++ b/src/libcamera/include/ipa_module.h\n@@ -7,7 +7,6 @@\n #ifndef __LIBCAMERA_IPA_MODULE_H__\n #define __LIBCAMERA_IPA_MODULE_H__\n \n-#include <memory>\n #include <string>\n \n #include <ipa/ipa_interface.h>\n@@ -30,7 +29,7 @@ public:\n \n \tbool load();\n \n-\tstd::unique_ptr<IPAInterface> createInstance();\n+\tstruct ipa_context *createContext();\n \n \tbool match(PipelineHandler *pipe,\n \t\t   uint32_t minVersion, uint32_t maxVersion) const;\n@@ -45,7 +44,7 @@ private:\n \tbool loaded_;\n \n \tvoid *dlHandle_;\n-\ttypedef IPAInterface *(*IPAIntfFactory)(void);\n+\ttypedef struct ipa_context *(*IPAIntfFactory)(void);\n \tIPAIntfFactory ipaCreate_;\n \n \tint loadIPAModuleInfo();\ndiff --git a/src/libcamera/include/meson.build b/src/libcamera/include/meson.build\nindex 697294f4b09b..17e2bed93fba 100644\n--- a/src/libcamera/include/meson.build\n+++ b/src/libcamera/include/meson.build\n@@ -9,6 +9,7 @@ libcamera_headers = files([\n     'device_enumerator_udev.h',\n     'event_dispatcher_poll.h',\n     'formats.h',\n+    'ipa_context_wrapper.h',\n     'ipa_manager.h',\n     'ipa_module.h',\n     'ipa_proxy.h',\ndiff --git a/src/libcamera/ipa_context_wrapper.cpp b/src/libcamera/ipa_context_wrapper.cpp\nnew file mode 100644\nindex 000000000000..66fc59b82373\n--- /dev/null\n+++ b/src/libcamera/ipa_context_wrapper.cpp\n@@ -0,0 +1,219 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * ipa_context_wrapper.cpp - Image Processing Algorithm context wrapper\n+ */\n+\n+#include \"ipa_context_wrapper.h\"\n+\n+#include <vector>\n+\n+#include <libcamera/controls.h>\n+\n+#include \"byte_stream_buffer.h\"\n+\n+/**\n+ * \\file ipa_context_wrapper.h\n+ * \\brief Image Processing Algorithm context wrapper\n+ */\n+\n+namespace libcamera {\n+\n+/**\n+ * \\class IPAContextWrapper\n+ * \\brief Wrap an ipa_context and expose it as an IPAInterface\n+ *\n+ * The IPAContextWrapper class wraps an ipa_context, provided by an IPA module, and\n+ * exposes an IPAInterface. This mechanism is used for IPAs that are not\n+ * isolated in a separate process to allow direct calls from pipeline handler\n+ * using the IPAInterface API instead of the lower-level ipa_context API.\n+ *\n+ * The IPAInterface methods are converted to the ipa_context API by translating\n+ * all C++ arguments into plain C structures or byte arrays that contain no\n+ * pointer, as required by the ipa_context API.\n+ */\n+\n+/**\n+ * \\brief Construct an IPAContextWrapper instance that wraps the \\a context\n+ * \\param[in] context The IPA module context\n+ *\n+ * Ownership of the \\a context is passed to the IPAContextWrapper. The context remains\n+ * valid for the whole lifetime of the wrapper and is destroyed automatically\n+ * with it.\n+ */\n+IPAContextWrapper::IPAContextWrapper(struct ipa_context *context)\n+\t: ctx_(context)\n+{\n+\tif (!ctx_)\n+\t\treturn;\n+\n+\tctx_->ops->register_callbacks(ctx_, &IPAContextWrapper::callbacks_,\n+\t\t\t\t      this);\n+}\n+\n+IPAContextWrapper::~IPAContextWrapper()\n+{\n+\tif (ctx_)\n+\t\tctx_->ops->destroy(ctx_);\n+}\n+\n+int IPAContextWrapper::init()\n+{\n+\tif (!ctx_)\n+\t\treturn 0;\n+\n+\tctx_->ops->init(ctx_);\n+\n+\treturn 0;\n+}\n+\n+void IPAContextWrapper::configure(const std::map<unsigned int, IPAStream> &streamConfig,\n+\t\t\t\t  const std::map<unsigned int, const ControlInfoMap &> &entityControls)\n+{\n+\tif (!ctx_)\n+\t\treturn;\n+\n+\tserializer_.reset();\n+\n+\t/* Translate the IPA stream configurations map. */\n+\tstruct ipa_stream c_streams[streamConfig.size()];\n+\n+\tunsigned int i = 0;\n+\tfor (const auto &stream : streamConfig) {\n+\t\tstruct ipa_stream *c_stream = &c_streams[i];\n+\t\tunsigned int id = stream.first;\n+\t\tconst IPAStream &ipaStream = stream.second;\n+\n+\t\tc_stream->id = id;\n+\t\tc_stream->pixel_format = ipaStream.pixelFormat;\n+\t\tc_stream->width = ipaStream.size.width;\n+\t\tc_stream->height = ipaStream.size.height;\n+\n+\t\t++i;\n+\t}\n+\n+\t/* Translate the IPA entity controls map. */\n+\tstruct ipa_control_info_map c_info_maps[entityControls.size()];\n+\tstd::vector<std::vector<uint8_t>> data(entityControls.size());\n+\n+\ti = 0;\n+\tfor (const auto &info : entityControls) {\n+\t\tstruct ipa_control_info_map &c_info_map = c_info_maps[i];\n+\t\tunsigned int id = info.first;\n+\t\tconst ControlInfoMap &infoMap = info.second;\n+\n+\t\tsize_t infoMapSize = serializer_.binarySize(infoMap);\n+\t\tdata[i].resize(infoMapSize);\n+\t\tByteStreamBuffer byteStream(data[i].data(), data[i].size());\n+\t\tserializer_.serialize(infoMap, byteStream);\n+\n+\t\tc_info_map.id = id;\n+\t\tc_info_map.data = byteStream.base();\n+\t\tc_info_map.size = byteStream.size();\n+\n+\t\t++i;\n+\t}\n+\n+\tctx_->ops->configure(ctx_, c_streams, streamConfig.size(),\n+\t\t\t     c_info_maps, entityControls.size());\n+}\n+\n+void IPAContextWrapper::mapBuffers(const std::vector<IPABuffer> &buffers)\n+{\n+\tif (!ctx_)\n+\t\treturn;\n+\n+\tstruct ipa_buffer c_buffers[buffers.size()];\n+\n+\tfor (unsigned int i = 0; i < buffers.size(); ++i) {\n+\t\tstruct ipa_buffer &c_buffer = c_buffers[i];\n+\t\tconst IPABuffer &buffer = buffers[i];\n+\t\tconst std::vector<Plane> &planes = buffer.memory.planes();\n+\n+\t\tc_buffer.id = buffer.id;\n+\t\tc_buffer.num_planes = planes.size();\n+\n+\t\tfor (unsigned int j = 0; j < planes.size(); ++j) {\n+\t\t\tconst Plane &plane = planes[j];\n+\t\t\tc_buffer.planes[j].dmabuf = plane.dmabuf();\n+\t\t\tc_buffer.planes[j].length = plane.length();\n+\t\t}\n+\t}\n+\n+\tctx_->ops->map_buffers(ctx_, c_buffers, buffers.size());\n+}\n+\n+void IPAContextWrapper::unmapBuffers(const std::vector<unsigned int> &ids)\n+{\n+\tif (!ctx_)\n+\t\treturn;\n+\n+\tctx_->ops->unmap_buffers(ctx_, ids.data(), ids.size());\n+}\n+\n+void IPAContextWrapper::processEvent(const IPAOperationData &data)\n+{\n+\tif (!ctx_)\n+\t\treturn;\n+\n+\tstruct ipa_operation_data c_data;\n+\tc_data.operation = data.operation;\n+\tc_data.data = data.data.data();\n+\tc_data.num_data = data.data.size();\n+\n+\tstruct ipa_control_list control_lists[data.controls.size()];\n+\tc_data.lists = control_lists;\n+\tc_data.num_lists = data.controls.size();\n+\n+\tstd::size_t listsSize = 0;\n+\tfor (const auto &list : data.controls)\n+\t\tlistsSize += serializer_.binarySize(list);\n+\n+\tstd::vector<uint8_t> binaryData(listsSize);\n+\tByteStreamBuffer byteStreamBuffer(binaryData.data(), listsSize);\n+\n+\tunsigned int i = 0;\n+\tfor (const auto &list : data.controls) {\n+\t\tstruct ipa_control_list &c_list = control_lists[i];\n+\t\tc_list.size = serializer_.binarySize(list);\n+\t\tByteStreamBuffer b = byteStreamBuffer.carveOut(c_list.size);\n+\n+\t\tserializer_.serialize(list, b);\n+\n+\t\tc_list.data = b.base();\n+\t}\n+\n+\tctx_->ops->process_event(ctx_, &c_data);\n+}\n+\n+void IPAContextWrapper::queue_frame_action(void *ctx, unsigned int frame,\n+\t\t\t\t\t   struct ipa_operation_data &data)\n+{\n+\tIPAContextWrapper *_this = static_cast<IPAContextWrapper *>(ctx);\n+\tIPAOperationData opData;\n+\n+\topData.operation = data.operation;\n+\tfor (unsigned int i = 0; i < data.num_data; ++i)\n+\t\topData.data.push_back(data.data[i]);\n+\n+\tfor (unsigned int i = 0; i < data.num_lists; ++i) {\n+\t\tconst struct ipa_control_list &c_list = data.lists[i];\n+\t\tByteStreamBuffer b(c_list.data, c_list.size);\n+\t\topData.controls.push_back(_this->serializer_.deserialize<ControlList>(b));\n+\t}\n+\n+\t_this->queueFrameAction.emit(frame, opData);\n+}\n+\n+#ifndef __DOXYGEN__\n+/*\n+ * This construct confuses Doygen and makes it believe that all members of the\n+ * operations is a member of IPAInterfaceWrapper. It must thus be hidden.\n+ */\n+const struct ipa_callback_ops IPAContextWrapper::callbacks_ = {\n+\t.queue_frame_action = &IPAContextWrapper::queue_frame_action,\n+};\n+#endif\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp\nindex f3180c0739cb..90eef12dbaf5 100644\n--- a/src/libcamera/ipa_manager.cpp\n+++ b/src/libcamera/ipa_manager.cpp\n@@ -12,6 +12,7 @@\n #include <string.h>\n #include <sys/types.h>\n \n+#include \"ipa_context_wrapper.h\"\n #include \"ipa_module.h\"\n #include \"ipa_proxy.h\"\n #include \"log.h\"\n@@ -30,6 +31,66 @@ LOG_DEFINE_CATEGORY(IPAManager)\n /**\n  * \\class IPAManager\n  * \\brief Manager for IPA modules\n+ *\n+ * The IPA module manager discovers IPA modules from disk, queries and loads\n+ * them, and creates IPA contexts. It supports isolation of the modules in a\n+ * separate process with IPC communication and offers a unified IPAInterface\n+ * view of the IPA contexts to pipeline handlers regardless of whether the\n+ * modules are isolated or loaded in the same process.\n+ *\n+ * Module isolation is based on the module licence. Open-source modules are\n+ * loaded without isolation, while closed-source module are forcefully isolated.\n+ * The isolation mechanism ensures that no code from a closed-source module is\n+ * ever run in the libcamera process.\n+ *\n+ * To create an IPA context, pipeline handlers call the IPAManager::ipaCreate()\n+ * method. For a directly loaded module, the manager calls the module's\n+ * ipaCreate() function directly and wraps the returned context in an\n+ * IPAContextWrapper that exposes an IPAInterface.\n+ *\n+ * ~~~~\n+ * +---------------+\n+ * |   Pipeline    |\n+ * |    Handler    |\n+ * +---------------+\n+ *         |\n+ *         v\n+ * +---------------+                   +---------------+\n+ * |      IPA      |                   |  Open Source  |\n+ * |   Interface   |                   |  IPA Module   |\n+ * | - - - - - - - |                   | - - - - - - - |\n+ * |  IPA Context  |  ipa_context_ops  |  ipa_context  |\n+ * |    Wrapper    | ----------------> |               |\n+ * +---------------+                   +---------------+\n+ * ~~~~\n+ *\n+ * For an isolated module, the manager instantiates an IPAProxy which spawns a\n+ * new process for an IPA proxy worker. The worker loads the IPA module and\n+ * creates the IPA context. The IPAProxy alse exposes an IPAInterface.\n+ *\n+ * ~~~~\n+ * +---------------+                   +---------------+\n+ * |   Pipeline    |                   | Closed Source |\n+ * |    Handler    |                   |  IPA Module   |\n+ * +---------------+                   | - - - - - - - |\n+ *         |                           |  ipa_context  |\n+ *         v                           |               |\n+ * +---------------+                   +---------------+\n+ * |      IPA      |           ipa_context_ops ^\n+ * |   Interface   |                           |\n+ * | - - - - - - - |                   +---------------+\n+ * |   IPA Proxy   |     operations    |   IPA Proxy   |\n+ * |               | ----------------> |    Worker     |\n+ * +---------------+      over IPC     +---------------+\n+ * ~~~~\n+ *\n+ * The IPAInterface implemented by the IPAContextWrapper or IPAProxy is\n+ * returned to the pipeline handler, and all interactions with the IPA context\n+ * go the same interface regardless of process isolation.\n+ *\n+ * In all cases the data passed to the IPAInterface methods is serialized to\n+ * Plain Old Data, either for the purpose of passing it to the IPA context\n+ * plain C API, or to transmit the data to the isolated process through IPC.\n  */\n \n IPAManager::IPAManager()\n@@ -199,7 +260,11 @@ std::unique_ptr<IPAInterface> IPAManager::createIPA(PipelineHandler *pipe,\n \tif (!m->load())\n \t\treturn nullptr;\n \n-\treturn m->createInstance();\n+\tstruct ipa_context *ctx = m->createContext();\n+\tif (!ctx)\n+\t\treturn nullptr;\n+\n+\treturn utils::make_unique<IPAContextWrapper>(ctx);\n }\n \n } /* namespace libcamera */\ndiff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp\nindex 99d308efd47b..2c355ea8b5e5 100644\n--- a/src/libcamera/ipa_module.cpp\n+++ b/src/libcamera/ipa_module.cpp\n@@ -385,13 +385,13 @@ const std::string &IPAModule::path() const\n /**\n  * \\brief Load the IPA implementation factory from the shared object\n  *\n- * The IPA module shared object implements an IPAInterface class to be used\n+ * The IPA module shared object implements an ipa_context object to be used\n  * by pipeline handlers. This method loads the factory function from the\n- * shared object. Later, createInstance() can be called to instantiate the\n- * IPAInterface.\n+ * shared object. Later, createContext() can be called to instantiate the\n+ * ipa_context.\n  *\n  * This method only needs to be called successfully once, after which\n- * createInstance() can be called as many times as IPAInterface instances are\n+ * createContext() can be called as many times as ipa_context instances are\n  * needed.\n  *\n  * Calling this function on an invalid module (as returned by isValid()) is\n@@ -433,24 +433,25 @@ bool IPAModule::load()\n }\n \n /**\n- * \\brief Instantiate an IPAInterface\n+ * \\brief Instantiate an IPA context\n  *\n- * After loading the IPA module with load(), this method creates an\n- * instance of the IPA module interface.\n+ * After loading the IPA module with load(), this method creates an instance of\n+ * the IPA module context. Ownership of the context is passed to the caller, and\n+ * the context shall be destroyed by calling the \\ref ipa_context_ops::destroy\n+ * \"ipa_context::ops::destroy()\" function.\n  *\n  * Calling this function on a module that has not yet been loaded, or an\n  * invalid module (as returned by load() and isValid(), respectively) is\n  * an error.\n  *\n- * \\return The IPA implementation as a new IPAInterface instance on success,\n- * or nullptr on error\n+ * \\return The IPA context on success, or nullptr on error\n  */\n-std::unique_ptr<IPAInterface> IPAModule::createInstance()\n+struct ipa_context *IPAModule::createContext()\n {\n \tif (!valid_ || !loaded_)\n \t\treturn nullptr;\n \n-\treturn std::unique_ptr<IPAInterface>(ipaCreate_());\n+\treturn ipaCreate_();\n }\n \n /**\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 59cf582580c4..c4f965bd7413 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -16,6 +16,7 @@ libcamera_sources = files([\n     'event_notifier.cpp',\n     'formats.cpp',\n     'geometry.cpp',\n+    'ipa_context_wrapper.cpp',\n     'ipa_controls.cpp',\n     'ipa_interface.cpp',\n     'ipa_manager.cpp',\ndiff --git a/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp b/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp\nindex a10761e52b32..07380c16e2d5 100644\n--- a/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp\n+++ b/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp\n@@ -72,9 +72,9 @@ int main(int argc, char **argv)\n \t}\n \tsocket.readyRead.connect(&readyRead);\n \n-\tstd::unique_ptr<IPAInterface> ipa = ipam->createInstance();\n-\tif (!ipa) {\n-\t\tLOG(IPAProxyLinuxWorker, Error) << \"Failed to create IPA interface\";\n+\tstruct ipa_context *ipac = ipam->createContext();\n+\tif (!ipac) {\n+\t\tLOG(IPAProxyLinuxWorker, Error) << \"Failed to create IPA context\";\n \t\treturn EXIT_FAILURE;\n \t}\n \n@@ -85,5 +85,7 @@ int main(int argc, char **argv)\n \twhile (1)\n \t\tdispatcher->processEvents();\n \n+\tipac->ops->destroy(ipac);\n+\n \treturn 0;\n }\n",
    "prefixes": [
        "libcamera-devel",
        "v2",
        "20/24"
    ]
}