{"id":9612,"url":"https://patchwork.libcamera.org/api/patches/9612/?format=json","web_url":"https://patchwork.libcamera.org/patch/9612/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20200915142038.28757-4-paul.elder@ideasonboard.com>","date":"2020-09-15T14:20:18","name":"[libcamera-devel,03/23] utils: ipc: add templates for code generation for IPC mechanism","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"9db8c3c5b4f2c800cfb3cb7dd0c6999aae692e8b","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/?format=json","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/9612/mbox/","series":[{"id":1291,"url":"https://patchwork.libcamera.org/api/series/1291/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=1291","date":"2020-09-15T14:20:16","name":"IPA isolation implementation","version":1,"mbox":"https://patchwork.libcamera.org/series/1291/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/9612/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/9612/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 58269C3B5D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 15 Sep 2020 14:21:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7E98562E32;\n\tTue, 15 Sep 2020 16:21:03 +0200 (CEST)","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 4DEA262E0F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Sep 2020 16:21:02 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2400:4051:61:600:2c71:1b79:d06d:5032])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DD40D276;\n\tTue, 15 Sep 2020 16:20:58 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"hcagVfXM\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1600179660;\n\tbh=WmmqVrXHBzIcvoKzMxXwCvSu1OjhoxDOgXFOGXqsnbI=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=hcagVfXMkiwWOKsAZB1IceXVG3q4ihr0N/5XJg8wdBfnoqJtOc193UBnT7ECkKn/Q\n\tH75DqAmR0HhupWSj12f2ujj6xtiJB7JxXLD0pToxjIa9uDVle82hB07iX4EVYxMDss\n\t5MmkJ1hiKHpY6qjeo3M3nG1Qoc3rUW1dVBXEPpJk=","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Tue, 15 Sep 2020 23:20:18 +0900","Message-Id":"<20200915142038.28757-4-paul.elder@ideasonboard.com>","X-Mailer":"git-send-email 2.27.0","In-Reply-To":"<20200915142038.28757-1-paul.elder@ideasonboard.com>","References":"<20200915142038.28757-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Subject":"[libcamera-devel] [PATCH 03/23] utils: ipc: add templates for code\n\tgeneration for IPC mechanism","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>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add templates to mojo to generate code for the IPC mechanism. These\ntemplates generate:\n- module header\n- module serializer\n- IPA proxy cpp, header, and worker\n\nGiven an input data definition mojom file for a pipeline.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n---\n .../module_generated.h.tmpl                   |  95 +++++\n .../module_ipa_proxy.cpp.tmpl                 | 219 ++++++++++\n .../module_ipa_proxy.h.tmpl                   | 108 +++++\n .../module_ipa_proxy_worker.cpp.tmpl          | 167 ++++++++\n .../module_serializer.h.tmpl                  |  43 ++\n .../libcamera_templates/proxy_functions.tmpl  | 180 ++++++++\n .../libcamera_templates/serializer.tmpl       | 262 ++++++++++++\n .../generators/mojom_libcamera_generator.py   | 403 ++++++++++++++++++\n 8 files changed, 1477 insertions(+)\n create mode 100644 utils/ipc/generators/libcamera_templates/module_generated.h.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl\n create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl\n create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py","diff":"diff --git a/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl b/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl\nnew file mode 100644\nindex 00000000..ea810406\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl\n@@ -0,0 +1,95 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) {{ year }}, Google Inc.\n+ *\n+ * {{ module_name }}_generated.h - Image Processing Algorithm interface for {{ module_name }}\n+ */\n+\n+// automatically generated by custom compiler\n+\n+#ifndef __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__\n+#define __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__\n+\n+#include <libcamera/ipa/ipa_interface.h>\n+\n+#include <libcamera/ipa/{{ module_name }}.h>\n+\n+{% if has_map %}#include <map>{% endif %}\n+{% if has_array %}#include <vector>{% endif %}\n+\n+namespace libcamera {\n+\n+enum {{ cmd_enum_name }} {\n+\tCMD_EXIT = 0,\n+{%- for method in interface_main.methods %}\n+\tCMD_{{ method.mojom_name|upper }} = {{loop.index}},\n+{%- endfor %}\n+{%- set count = interface_main.methods|length -%}\n+{%- for method in interface_cb.methods %}\n+\tCMD_{{ method.mojom_name|upper }} = {{loop.index + count}},\n+{%- endfor %}\n+};\n+\n+{% for enum in enums %}\n+enum {{ enum.mojom_name }} {\n+{%- for field in enum.fields %}\n+\t{{ field.mojom_name }} = {{ field.numeric_value + 1 }},\n+{%- endfor %}\n+};\n+{% endfor %}\n+\n+{%- for struct in structs_nonempty %}\n+struct {{ struct.mojom_name }}\n+{\n+public:\n+\t{{ struct.mojom_name }}() {%- if struct|has_default_fields %} :{% endif %}\n+{%- for field in struct.fields|with_default_values -%}\n+{{\" \" if loop.first}}{{ field.mojom_name}}_({{ field|default_value }}){{ \", \" if not loop.last }}\n+{%- endfor %} {}\n+\t~{{ struct.mojom_name }}() {}\n+\n+\t{{ struct.mojom_name }}(\n+{%- for field in struct.fields -%}\n+{{ field|name }} {{ field.mojom_name }}{{ \", \" if not loop.last }}\n+{%- endfor -%}\n+) : \n+{%- for field in struct.fields -%}\n+{{\" \" if loop.first}}{{ field.mojom_name}}_({{ field.mojom_name }}){{ \", \" if not loop.last }}\n+{%- endfor %} {}\n+{% for field in struct.fields %}\n+\t{{ field|name }} {{ field.mojom_name }}_;\n+{%- endfor %}\n+};\n+{% endfor %}\n+\n+{#-\n+What to do about the #includes? Should we forward-declare, or\n+require {{module_name}}.h to include the required headers?\n+For now I'm going for the latter, coz this header essentially\n+extends {{module_name}}.h.\n+#}\n+class {{ interface_name }} : public IPAInterface\n+{\n+public:\n+\tvirtual ~{{interface_name}}() {};\n+{% for method in interface_main.methods %}\n+\tvirtual {{method|method_return_value}} {{method.mojom_name}}(\n+{%- for param in method|method_parameters %}\n+\t\t{{param}}{{- \",\" if not loop.last}}\n+{%- endfor -%}\n+) = 0;\n+{% endfor %}\n+\n+{%- for method in interface_cb.methods %}\n+\tSignal<\n+{%- for param in method.parameters -%}\n+\t\tconst {{param|name}}{{\" &\" if not param|is_pod}}\n+\t\t{{- \", \" if not loop.last }}\n+{%- endfor -%}\n+> {{method.mojom_name}};\n+{% endfor -%}\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__ */\ndiff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl\nnew file mode 100644\nindex 00000000..bc8b34e5\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl\n@@ -0,0 +1,219 @@\n+{%- import \"proxy_functions.tmpl\" as proxy_funcs -%}\n+\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) {{ year }}, Google Inc.\n+ *\n+ * ipa_proxy_{{ module_name }}.cpp - Image Processing Algorithm proxy for {{ module_name }}\n+ */\n+\n+// automatically generated by custom compiler\n+\n+#include <vector>\n+\n+#include <libcamera/ipa/ipa_interface.h>\n+#include <libcamera/ipa/ipa_module_info.h>\n+#include <libcamera/ipa/{{module_name}}.h>\n+#include <libcamera/ipa/{{module_name}}_generated.h>\n+#include <libcamera/ipa/{{module_name}}_serializer.h>\n+\n+#include \"libcamera/internal/control_serializer.h\"\n+#include \"libcamera/internal/ipa_ipc.h\"\n+#include \"libcamera/internal/ipa_ipc_unixsocket.h\"\n+#include \"libcamera/internal/ipa_data_serializer.h\"\n+#include \"libcamera/internal/ipa_module.h\"\n+#include \"libcamera/internal/ipa_proxy.h\"\n+#include \"libcamera/internal/ipc_unixsocket.h\"\n+#include \"libcamera/internal/log.h\"\n+#include \"libcamera/internal/process.h\"\n+#include \"libcamera/internal/thread.h\"\n+\n+#include <libcamera/ipa/ipa_proxy_{{module_name}}.h>\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(IPAProxy)\n+\n+{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)\n+\t: IPAProxy(ipam), running_(false),\n+\t  isolate_(isolate)\n+{\n+\tLOG(IPAProxy, Debug)\n+\t\t<< \"initializing dummy proxy: loading IPA from \"\n+\t\t<< ipam->path();\n+\n+\tif (isolate_) {\n+\t\tconst std::string proxy_worker_path = resolvePath(\"ipa_proxy_{{module_name}}\");\n+\t\tif (proxy_worker_path.empty()) {\n+\t\t\tLOG(IPAProxy, Error)\n+\t\t\t\t<< \"Failed to get proxy worker path\";\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxy_worker_path.c_str());\n+\t\tif (!ipc_->isValid()) {\n+\t\t\tLOG(IPAProxy, Error) << \"Failed to create IPAIPC\";\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tipc_->recvIPC.connect(this, &{{proxy_name}}::recvIPC);\n+\n+\t\tvalid_ = true;\n+\t\treturn;\n+\t}\n+\n+\tif (!ipam->load())\n+\t\treturn;\n+\n+\tIPAInterface *ipai = ipam->createInterface();\n+\tif (!ipai) {\n+\t\tLOG(IPAProxy, Error)\n+\t\t\t<< \"Failed to create IPA context for \" << ipam->path();\n+\t\treturn;\n+\t}\n+\n+\tipa_ = std::unique_ptr<{{interface_name}}>(dynamic_cast<{{interface_name}} *>(ipai));\n+\tproxy_.setIPA(ipa_.get());\n+\n+{% for method in interface_cb.methods %}\n+\tipa_->{{method.mojom_name}}.connect(this, &{{proxy_name}}::{{method.mojom_name}}Thread);\n+{%- endfor %}\n+\n+\tvalid_ = true;\n+}\n+\n+{{proxy_name}}::~{{proxy_name}}()\n+{\n+\tif (isolate_)\n+\t\tipc_->sendAsync(CMD_EXIT, {}, {});\n+}\n+\n+void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds)\n+{\n+\tuint32_t cmd = (data[0] & 0xff) |\n+\t\t       ((data[1] & 0xff) << 8) |\n+\t\t       ((data[2] & 0xff) << 16) |\n+\t\t       ((data[3] & 0xff) << 24);\n+\n+\t/* Need to skip another 4 bytes for the sequence number. */\n+\tstd::vector<uint8_t> vec(data.begin() + 8, data.end());\n+{% if interface_cb.methods|length > 0 %}\n+\tswitch (cmd) {\n+{%- for method in interface_cb.methods %}\n+\tcase CMD_{{method.mojom_name|upper}}: {\n+\t\t{{method.mojom_name}}IPC(vec, fds);\n+\t\tbreak;\n+\t}\n+{%- endfor %}\n+\t}\n+{%- endif %}\n+}\n+\n+{% for method in interface_main.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method)}}\n+{\n+\tif (isolate_)\n+\t\t{{\"return \" if method|method_return_value != \"void\"}}{{method.mojom_name}}IPC(\n+{%- for param in method|method_param_names -%}\n+\t\t{{param}}{{- \", \" if not loop.last}}\n+{%- endfor -%}\n+);\n+\telse\n+\t\t{{\"return \" if method|method_return_value != \"void\"}}{{method.mojom_name}}Thread(\n+{%- for param in method|method_param_names -%}\n+\t\t{{param}}{{- \", \" if not loop.last}}\n+{%- endfor -%}\n+);\n+}\n+\n+{{proxy_funcs.func_sig(proxy_name, method, \"Thread\")}}\n+{\n+{%- if method.mojom_name == \"init\" %}\n+\t{{proxy_funcs.init_thread_body()}}\n+{% elif method.mojom_name == \"start\" %}\n+\t{{proxy_funcs.start_thread_body()}}\n+{% elif method.mojom_name == \"stop\" %}\n+\t{{proxy_funcs.stop_thread_body()}}\n+{% elif not method|is_async %}\n+\tipa_->{{method.mojom_name}}(\n+\t{%- for param in method|method_param_names -%}\n+\t\t{{param}}{{- \", \" if not loop.last}}\n+\t{%- endfor -%}\n+);\n+{% elif method|is_async %}\n+\tproxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued,\n+\t{%- for param in method|method_param_names -%}\n+\t\t{{param}}{{- \", \" if not loop.last}}\n+\t{%- endfor -%}\n+);\n+{%- endif %}\n+}\n+\n+{{proxy_funcs.func_sig(proxy_name, method, \"IPC\")}}\n+{\n+{%- set has_input = true if method|method_param_inputs|length > 0 %}\n+{%- set has_output = true if method|method_param_outputs|length > 0 or method|method_return_value != \"void\" %}\n+{% if has_input %}\n+\tstd::vector<uint8_t> _ipcInputBuf;\n+{%- if method|method_input_has_fd %}\n+\tstd::vector<int32_t> _ipcInputFds;\n+{%- endif %}\n+{%- endif %}\n+{%- if has_output %}\n+\tstd::vector<uint8_t> _ipcOutputBuf;\n+{%- if method|method_output_has_fd %}\n+\tstd::vector<int32_t> _ipcOutputFds;\n+{%- endif %}\n+{%- endif %}\n+\n+{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf', '_ipcInputFds')}}\n+\n+{%- set input_buf = \"_ipcInputBuf\" if has_input else \"{}\" %}\n+{%- set fds_buf = \"_ipcInputFds\" if method|method_input_has_fd else \"{}\" %}\n+{%- set cmd = \"CMD_\" + method.mojom_name|upper %}\n+{% if method|is_async %}\n+\tint ret = ipc_->sendAsync({{cmd}}, {{input_buf}}, {{fds_buf}});\n+{%- else %}\n+\tint ret = ipc_->sendSync({{cmd}}, {{input_buf}}, {{fds_buf}}\n+{{- \", &_ipcOutputBuf\" if has_output -}}\n+{{- \", &_ipcOutputFds\" if has_output and method|method_output_has_fd -}}\n+);\n+{%- endif %}\n+\tif (ret < 0) {\n+\t\tLOG(IPAProxy, Error) << \"Failed to call {{method.mojom_name}}\";\n+{%- if method|method_return_value == \"void\" %}\n+\t\treturn;\n+{%- else %}\n+\t\treturn static_cast<{{method|method_return_value}}>(ret);\n+{%- endif %}\n+\t}\n+\tLOG(IPAProxy, Debug) << \"Done calling {{method.mojom_name}}\";\n+{% if method|method_return_value != \"void\" %}\n+\treturn static_cast<{{method.response_parameters|first|name}}>(readUInt<{{method.response_parameters|first|name}}>(_ipcOutputBuf, 0));\n+{% elif method|method_param_outputs|length > 0 %}\n+{{proxy_funcs.deserialize_call(method|method_param_outputs, '_ipcOutputBuf', '_ipcOutputFds')}}\n+{% endif -%}\n+}\n+\n+{% endfor %}\n+\n+\n+{% for method in interface_cb.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method, \"Thread\")}}\n+{\n+\t{{method.mojom_name}}.emit({{method.parameters|params_comma_sep}});\n+}\n+\n+void {{proxy_name}}::{{method.mojom_name}}IPC(\n+\tstd::vector<uint8_t> &data,\n+\t[[maybe_unused]] std::vector<int32_t> &fds)\n+{ \n+{%- for param in method.parameters %}\n+\t{{param|name}} {{param.mojom_name}};\n+{%- endfor %}\n+{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false)}}\n+\t{{method.mojom_name}}.emit({{method.parameters|params_comma_sep}});\n+}\n+{% endfor %}\n+\n+} /* namespace libcamera */\ndiff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl\nnew file mode 100644\nindex 00000000..432fefa9\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl\n@@ -0,0 +1,108 @@\n+{%- import \"proxy_functions.tmpl\" as proxy_funcs -%}\n+\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) {{ year }}, Google Inc.\n+ *\n+ * ipa_proxy_{{ module_name }}.h - Image Processing Algorithm proxy for {{ module_name }}\n+ */\n+\n+// automatically generated by custom compiler\n+\n+#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__\n+#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__\n+\n+// TODO move this to a proxy header directory\n+\n+#include <libcamera/ipa/ipa_interface.h>\n+#include <libcamera/ipa/{{module_name}}.h>\n+#include <libcamera/ipa/{{module_name}}_generated.h>\n+\n+#include \"libcamera/internal/control_serializer.h\"\n+#include \"libcamera/internal/ipa_ipc.h\"\n+#include \"libcamera/internal/ipa_ipc_unixsocket.h\"\n+#include \"libcamera/internal/ipa_proxy.h\"\n+#include \"libcamera/internal/ipc_unixsocket.h\"\n+#include \"libcamera/internal/thread.h\"\n+\n+namespace libcamera {\n+\n+class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object\n+{\n+public:\n+\t{{proxy_name}}(IPAModule *ipam, bool isolate);\n+\t~{{proxy_name}}();\n+\n+{% for method in interface_main.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method, \"\", false, true)|indent(8, true)}};\n+{% endfor %}\n+\n+{%- for method in interface_cb.methods %}\n+\tSignal<\n+{%- for param in method.parameters -%}\n+\t\tconst {{param|name}}{{\" &\" if not param|is_pod}}\n+\t\t{{- \", \" if not loop.last }}\n+{%- endfor -%}\n+> {{method.mojom_name}};\n+{% endfor %}\n+\n+private:\n+\tvoid recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds);\n+\n+{% for method in interface_main.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method, \"Thread\", false)|indent(8, true)}};\n+{{proxy_funcs.func_sig(proxy_name, method, \"IPC\", false)|indent(8, true)}};\n+{% endfor %}\n+{% for method in interface_cb.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method, \"Thread\", false)|indent(8, true)}};\n+\tvoid {{method.mojom_name}}IPC(\n+\t\tstd::vector<uint8_t> &data,\n+\t\tstd::vector<int32_t> &fds);\n+{% endfor %}\n+\n+\t/* Helper class to invoke async functions in another thread. */\n+\tclass ThreadProxy : public Object\n+\t{\n+\tpublic:\n+\t\tvoid setIPA(IPARPiInterface *ipa)\n+\t\t{\n+\t\t\tipa_ = ipa;\n+\t\t}\n+\n+\t\tint start()\n+\t\t{\n+\t\t\treturn ipa_->start();\n+\t\t}\n+\n+\t\tvoid stop()\n+\t\t{\n+\t\t\tipa_->stop();\n+\t\t}\n+{% for method in interface_main.methods %}\n+{%- if method|is_async %}\n+\t\t{{proxy_funcs.func_sig(proxy_name, method, \"\", false)|indent(16)}}\n+\t\t{\n+\t\t\tipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});\n+\t\t}\n+{%- endif %}\n+{%- endfor %}\n+\n+\tprivate:\n+\t\t{{interface_name}} *ipa_;\n+\t};\n+\n+\tbool running_;\n+\tThread thread_;\n+\tThreadProxy proxy_;\n+\tstd::unique_ptr<{{interface_name}}> ipa_;\n+\n+\tconst bool isolate_;\n+\n+\tstd::unique_ptr<IPAIPCUnixSocket> ipc_;\n+\n+\tControlSerializer controlSerializer_;\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__ */\ndiff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl\nnew file mode 100644\nindex 00000000..22d0ca1f\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl\n@@ -0,0 +1,167 @@\n+{%- import \"proxy_functions.tmpl\" as proxy_funcs -%}\n+\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) {{ year }}, Google Inc.\n+ *\n+ * ipa_proxy_{{ module_name }}_worker.cpp - Image Processing Algorithm proxy worker for {{ module_name }}\n+ */\n+\n+// automatically generated by custom compiler\n+\n+#include <algorithm>\n+#include <iostream>\n+#include <sys/types.h>\n+#include <tuple>\n+#include <unistd.h>\n+\n+#include <libcamera/event_dispatcher.h>\n+#include <libcamera/ipa/ipa_interface.h>\n+#include <libcamera/ipa/{{module_name}}_generated.h>\n+#include <libcamera/ipa/{{module_name}}_serializer.h>\n+#include <libcamera/logging.h>\n+\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/control_serializer.h\"\n+#include \"libcamera/internal/ipa_data_serializer.h\"\n+#include \"libcamera/internal/ipa_ipc_unixsocket.h\"\n+#include \"libcamera/internal/ipa_module.h\"\n+#include \"libcamera/internal/ipa_proxy.h\"\n+#include \"libcamera/internal/ipc_unixsocket.h\"\n+#include \"libcamera/internal/log.h\"\n+#include \"libcamera/internal/thread.h\"\n+\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY({{proxy_name}}Worker)\n+\n+struct CallData {\n+\tIPCUnixSocket::Payload *response;\n+\tbool done;\n+};\n+\n+IPARPiInterface *ipa_;\n+IPCUnixSocket socket_;\n+std::map<uint32_t, struct CallData> callData_;\n+\n+ControlSerializer controlSerializer_;\n+\n+bool exit_ = false;\n+\n+void readyRead(IPCUnixSocket *socket)\n+{\n+\tIPCUnixSocket::Payload _message, _response;\n+\tint _ret = socket->receive(&_message);\n+\tif (_ret) {\n+\t\tLOG({{proxy_name}}Worker, Error)\n+\t\t\t<< \"Receive message failed\" << _ret;\n+\t\treturn;\n+\t}\n+\n+\tuint32_t _cmd, _seq;\n+\tstd::tie(_cmd, _seq) = readHeader(_message);\n+\teraseHeader(_message);\n+\n+\tswitch (_cmd) {\n+\tcase CMD_EXIT: {\n+\t\texit_ = true;\n+\t\tbreak;\n+\t}\n+\n+{% for method in interface_main.methods %}\n+\tcase CMD_{{method.mojom_name|upper}}: {\n+\t{{proxy_funcs.deserialize_call(method|method_param_inputs, '_message.data', '_message.fds', false, true)|indent(8, true)}}\n+{% for param in method|method_param_outputs %}\n+\t\t{{param|name}} {{param.mojom_name}};\n+{% endfor %}\n+\t\tipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}\n+{{- \", \" if method|method_param_outputs|params_comma_sep-}}\n+{%- for param in method|method_param_outputs -%}\n+&{{param.mojom_name}}{{\", \" if not loop.last}}\n+{%- endfor -%}\n+);\n+{% if not method|is_async %}\n+\t\twriteHeader(_response, _cmd, _seq);\n+\t{{proxy_funcs.serialize_call(method|method_param_outputs, \"_response.data\", \"_response.fds\")|indent(8, true)}}\n+\t\tint _ret = socket_.send(_response);\n+\t\tif (_ret < 0) {\n+\t\t\tLOG({{proxy_name}}Worker, Error)\n+\t\t\t\t<< \"Reply to {{method.mojom_name}}() failed\" << _ret;\n+\t\t}\n+\t\tLOG({{proxy_name}}Worker, Debug) << \"Done replying to {{method.mojom_name}}()\";\n+\n+\t\tbreak;\n+{%- endif %}\n+\t}\n+{% endfor %}\n+\t}\n+}\n+\n+{% for method in interface_cb.methods %}\n+{{proxy_funcs.func_sig(proxy_name, method, \"\", false)}}\n+{\n+\tIPCUnixSocket::Payload _message;\n+\n+\twriteHeader(_message, CMD_{{method.mojom_name|upper}}, 0);\n+\t{{proxy_funcs.serialize_call(method|method_param_inputs, \"_message.data\", \"_message.fds\")}}\n+\n+\tsocket_.send(_message);\n+\n+\tLOG(IPAProxyRPiWorker, Debug) << \"{{method.mojom_name}} done\";\n+}\n+{%- endfor %}\n+\n+int main(int argc, char **argv)\n+{\n+\t/* Uncomment this for debugging. */\n+#if 0\n+\tstd::string logPath = \"/tmp/libcamera.worker.\" +\n+\t\t\t      std::to_string(getpid()) + \".log\";\n+\tlogSetFile(logPath.c_str());\n+#endif\n+\n+\tif (argc < 3) {\n+\t\tLOG({{proxy_name}}Worker, Debug)\n+\t\t\t<< \"Tried to start worker with no args\";\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\tint fd = std::stoi(argv[2]);\n+\tLOG({{proxy_name}}Worker, Debug)\n+\t\t<< \"Starting worker for IPA module \" << argv[1]\n+\t\t<< \" with IPC fd = \" << fd;\n+\n+\tstd::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]);\n+\tif (!ipam->isValid() || !ipam->load()) {\n+\t\tLOG({{proxy_name}}Worker, Error)\n+\t\t\t<< \"IPAModule \" << argv[1] << \" should be valid but isn't\";\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\tif (socket_.bind(fd) < 0) {\n+\t\tLOG({{proxy_name}}Worker, Error) << \"IPC socket binding failed\";\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\tsocket_.readyRead.connect(&readyRead);\n+\n+\tipa_ = dynamic_cast<{{interface_name}} *>(ipam->createInterface());\n+\tif (!ipa_) {\n+\t\tLOG({{proxy_name}}Worker, Error) << \"Failed to create IPA interface instance\";\n+\t\treturn EXIT_FAILURE;\n+\t}\n+{% for method in interface_cb.methods %}\n+\tipa_->{{method.mojom_name}}.connect(&{{method.mojom_name}});\n+{%- endfor %}\n+\n+\tLOG({{proxy_name}}Worker, Debug) << \"Proxy worker successfully started\";\n+\n+\t/* \\todo upgrade listening loop */\n+\tEventDispatcher *dispatcher = Thread::current()->eventDispatcher();\n+\twhile (!exit_)\n+\t\tdispatcher->processEvents();\n+\n+\tdelete ipa_;\n+\tsocket_.close();\n+\n+\treturn 0;\n+}\ndiff --git a/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl\nnew file mode 100644\nindex 00000000..8e3c24d6\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl\n@@ -0,0 +1,43 @@\n+{%- import \"serializer.tmpl\" as serializer -%}\n+\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) {{ year }}, Google Inc.\n+ *\n+ * {{ module_name }}_serializer.h - Image Processing Algorithm data serializer for {{ module_name }}\n+ */\n+\n+// automatically generated by custom compiler\n+\n+#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__\n+#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__\n+\n+#include <libcamera/ipa/{{ module_name }}.h>\n+#include <libcamera/ipa/{{ module_name }}_generated.h>\n+\n+#include \"libcamera/internal/control_serializer.h\"\n+#include \"libcamera/internal/ipa_data_serializer.h\"\n+\n+#include <tuple>\n+#include <vector>\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(IPADataSerializer)\n+{% for struct in structs_nonempty %}\n+template<>\n+class IPADataSerializer<{{ struct.mojom_name }}>\n+{\n+public:\n+{{- serializer.serializer(struct, base_controls) }}\n+{%- if struct|has_fd %}\n+{{ serializer.deserializer_fd(struct) }}\n+{%- else %}\n+{{ serializer.deserializer_no_fd(struct) }}\n+{%- endif %}\n+};\n+{% endfor %}\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__ */\ndiff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl\nnew file mode 100644\nindex 00000000..5ddc715b\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl\n@@ -0,0 +1,180 @@\n+{#\n+ # \\brief Generate fuction prototype\n+ #\n+ # \\param class Class name\n+ # \\param method mojom Method object\n+ # \\param suffix Suffix to append to \\a method function name\n+ # \\param need_class_name True to generate class name with function\n+ # \\param override True to generate override tag after the function prototype\n+ #}\n+{%- macro func_sig(class, method, suffix, need_class_name = true, override = false) -%}\n+{{method|method_return_value}} {{ class + \"::\" if need_class_name }}{{method.mojom_name}}{{suffix}}(\n+{%- for param in method|method_parameters %}\n+\t{{param}}{{- \",\" if not loop.last}}\n+{%- endfor -%}\n+){{\" override\" if override}}\n+{%- endmacro -%}\n+\n+{#\n+ # \\brief Generate function body for IPA init() function for thread\n+ #}\n+{%- macro init_thread_body() -%}\n+\tint ret = ipa_->init(settings);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tproxy_.moveToThread(&thread_);\n+\n+\treturn 0;\n+{%- endmacro -%}\n+\n+{#\n+ # \\brief Generate function body for IPA start() function for thread\n+ #}\n+{%- macro start_thread_body() -%}\n+\trunning_ = true;\n+\tthread_.start();\n+\n+\treturn proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking);\n+{%- endmacro -%}\n+\n+{#\n+ # \\brief Generate function body for IPA stop() function for thread\n+ #}\n+{%- macro stop_thread_body() -%}\n+\tif (!running_)\n+\t\treturn;\n+\n+\trunning_ = false;\n+\n+\tproxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking);\n+\n+\tthread_.exit();\n+\tthread_.wait();\n+{%- endmacro -%}\n+\n+\n+{#\n+ # \\brief Serialize multiple objects into data buffer and fd vector\n+ #\n+ # Generate code to serialize multiple objects, as specified in \\a params\n+ # (which are the parameters to some function), into \\a buf data buffer and\n+ # \\a fds fd vector.\n+ # This code is meant to be used by the proxy, for serializing prior to IPC calls.\n+ #}\n+{%- macro serialize_call(params, buf, fds) %}\n+{%- for param in params %}\n+\tstd::vector<uint8_t> {{param.mojom_name}}Buf;\n+{%- if param|has_fd %}\n+\tstd::vector<int32_t> {{param.mojom_name}}Fds;\n+\tstd::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) = \n+{%- else %}\n+\tstd::tie({{param.mojom_name}}Buf, std::ignore) = \n+{%- endif %}\n+\t\tIPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}\n+{{- \", &controlSerializer_\" if param|needs_control_serializer -}}\n+);\n+{%- endfor %}\n+\n+{%- if params|length > 1 %}\n+{%- for param in params %}\n+\tappendUInt<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size());\n+{%- if param|has_fd %}\n+\tappendUInt<uint32_t>({{buf}}, {{param.mojom_name}}Fds.size());\n+{%- endif %}\n+{%- endfor %}\n+{%- endif %}\n+\n+{%- for param in params %}\n+\t{{buf}}.insert({{buf}}.end(), {{param.mojom_name}}Buf.begin(), {{param.mojom_name}}Buf.end());\n+{%- endfor %}\n+\n+{%- for param in params %}\n+{%- if param|has_fd %}\n+\t{{fds}}.insert({{fds}}.end(), {{param.mojom_name}}Fds.begin(), {{param.mojom_name}}Fds.end());\n+{%- endif %}\n+{%- endfor %}\n+{%- endmacro -%}\n+\n+\n+{#\n+ # \\brief Deserialize a single object from data buffer and fd vector\n+ #\n+ # \\param pointer True deserializes the object into a dereferenced pointer\n+ #\n+ # Generate code to deserialize a single object, as specified in \\a param,\n+ # from \\a buf data buffer and \\a fds fd vector.\n+ # This code is meant to be used by macro deserialize_call.\n+ #}\n+{%- macro deserialize_param(param, pointer, loop, buf, fds) -%}\n+{{\"*\" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize(\n+\t{{buf}}.begin() + {{param.mojom_name}}Start,\n+{%- if loop.last %}\n+\t{{buf}}.end()\n+{%- else %}\n+\t{{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize\n+{%- endif -%}\n+{{- \",\" if param|has_fd }}\n+{%- if param|has_fd %}\n+\t{{fds}}.begin() + {{param.mojom_name}}FdStart,\n+{%- if loop.last %}\n+\t{{fds}}.end()\n+{%- else %}\n+\t{{fds}}.begin() + {{param.mojom_name}}FdStart + {{param.mojom_name}}FdsSize\n+{%- endif -%}\n+{%- endif -%}\n+{{- \",\" if param|needs_control_serializer }}\n+{%- if param|needs_control_serializer %}\n+\t&controlSerializer_\n+{%- endif -%}\n+);\n+{%- endmacro -%}\n+\n+\n+{#\n+ # \\brief Deserialize multiple objects from data buffer and fd vector\n+ #\n+ # \\param pointer True deserializes objects into pointers, and adds a null check.\n+ # \\param declare True declares the objects in addition to deserialization.\n+ #\n+ # Generate code to deserialize multiple objects, as specified in \\a params\n+ # (which are the parameters to some function), from \\a buf data buffer and\n+ # \\a fds fd vector.\n+ # This code is meant to be used by the proxy, for serializing prior to IPC calls.\n+ #}\n+{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false) -%}\n+{% set ns = namespace(size_offset = 0) %}\n+{%- if params|length > 1 %}\n+{%- for param in params %}\n+\t[[maybe_unused]] size_t {{param.mojom_name}}BufSize = readUInt<uint32_t>({{buf}}, {{ns.size_offset}});\n+\t{%- set ns.size_offset = ns.size_offset + 4 %}\n+{%- if param|has_fd %}\n+\t[[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readUInt<uint32_t>({{buf}}, {{ns.size_offset}});\n+\t{%- set ns.size_offset = ns.size_offset + 4 %}\n+{%- endif %}\n+{%- endfor %}\n+{%- endif %}\n+{% for param in params %}\n+{%- if loop.first %}\n+\tsize_t {{param.mojom_name}}Start = {{ns.size_offset}};\n+{%- else %}\n+\tsize_t {{param.mojom_name}}Start = {{loop.previtem.mojom_name}}Start + {{loop.previtem.mojom_name}}BufSize;\n+{%- endif %}\n+{%- endfor %}\n+{% for param in params|with_fds %}\n+{%- if loop.first %}\n+\tsize_t {{param.mojom_name}}FdStart = 0;\n+{%- elif not loop.last %}\n+\tsize_t {{param.mojom_name}}FdStart = {{loop.previtem.mojom_name}}FdStart + {{loop.previtem.mojom_name}}FdsSize;\n+{%- endif %}\n+{%- endfor %}\n+{% for param in params %}\n+\t{%- if pointer %}\n+\tif ({{param.mojom_name}}) {\n+{{deserialize_param(param, pointer, loop, buf, fds)|indent(16, True)}}\n+\t}\n+\t{%- else %}\n+\t{{param|name + \" \" if declare}}{{deserialize_param(param, pointer, loop, buf, fds)|indent(8)}}\n+\t{%- endif %}\n+{% endfor %}\n+{%- endmacro -%}\ndiff --git a/utils/ipc/generators/libcamera_templates/serializer.tmpl b/utils/ipc/generators/libcamera_templates/serializer.tmpl\nnew file mode 100644\nindex 00000000..a1b7b9f8\n--- /dev/null\n+++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl\n@@ -0,0 +1,262 @@\n+{# Turn this into a C macro? #}\n+{#\n+ # \\brief Verify that there is enough bytes to deserialize\n+ #\n+ # Generate code that verifies that \\a size is not greater than \\a dataSize.\n+ # Otherwise log an error with \\a name and \\a typename.\n+ #}\n+{%- macro check_data_size(size, dataSize, name, typename) %}\n+\t\tif ({{size}} > {{dataSize}}) {\n+\t\t\tLOG(IPADataSerializer, Error)\n+\t\t\t\t<< \"Failed to deserialize {{name}}: not enough {{typename}}, expected \"\n+\t\t\t\t<< ({{size}}) << \", got \" << ({{dataSize}});\n+\t\t\treturn ret;\n+\t\t}\n+{%- endmacro %}\n+\n+\n+{#\n+ # \\brief Serialize some field into return vector\n+ #\n+ # Generate code to serialize \\a field into ret_data, including size of the\n+ # field and fds (where appropriate). \\a base_controls indicates the\n+ # default ControlInfoMap in the event that the ControlList does not have one.\n+ # This code is meant to be used by the IPADataSerializer specialization.\n+ #}\n+{%- macro serializer_field(field, base_controls, loop) %}\n+{%- if field|is_pod %}\n+\t\tappendUInt<{{field|name}}>(ret_data, static_cast<uint{{field|bit_width}}_t>(data.{{field.mojom_name}}_));\n+{%- elif field|is_enum %}\n+\t\tappendUInt<uint{{field|bit_width}}_t>(ret_data, static_cast<uint{{field|bit_width}}_t>(data.{{field.mojom_name}}_));\n+{%- elif field|is_fd %}\n+\t\tstd::vector<uint8_t> {{field.mojom_name}};\n+\t\tstd::vector<int32_t> {{field.mojom_name}}Fds;\n+\t\tstd::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =\n+\t\t\tIPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_);\n+\t\tret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());\n+\t\tret_fds.insert(ret_fds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());\n+{%- elif field|is_controls %}\n+\t\tif (data.{{field.mojom_name}}_.size() > 0) {\n+\t\t\tstd::vector<uint8_t> {{field.mojom_name}};\n+\t\t\tstd::tie({{field.mojom_name}}, std::ignore) =\n+\t\t\t\tIPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_,\n+\t\t\t\t\tdata.{{field.mojom_name}}_.infoMap() ? *data.{{field.mojom_name}}_.infoMap() : {{base_controls}},\n+\t\t\t\t\tcs);\n+\t\t\tappendUInt<uint32_t>(ret_data, {{field.mojom_name}}.size());\n+\t\t\tret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());\n+\t\t} else {\n+\t\t\tappendUInt<uint32_t>(ret_data, 0);\n+\t\t}\n+{%- elif field|is_plain_struct or field|is_array or field|is_map %}\n+\t\tstd::vector<uint8_t> {{field.mojom_name}};\n+\t{%- if field|has_fd %}\n+\t\tstd::vector<int32_t> {{field.mojom_name}}Fds;\n+\t\tstd::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =\n+\t{%- else %}\n+\t\tstd::tie({{field.mojom_name}}, std::ignore) =\n+\t{%- endif %}\n+\t\t\tIPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs);\n+\t\tappendUInt<uint32_t>(ret_data, {{field.mojom_name}}.size());\n+\t{%- if field|has_fd %}\n+\t\tappendUInt<uint32_t>(ret_data, {{field.mojom_name}}Fds.size());\n+\t{%- endif %}\n+\t\tret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());\n+\t{%- if field|has_fd %}\n+\t\tret_fds.insert(ret_fds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());\n+\t{%- endif %}\n+{%- else %}\n+\t\t/* Unknown serialization for {{field.mojom_name}}. */\n+{%- endif %}\n+{%- endmacro %}\n+\n+\n+{#\n+ # \\brief Deserialize some field into return struct\n+ #\n+ # Generate code to deserialize \\a field into object ret.\n+ # This code is meant to be used by the IPADataSerializer specialization.\n+ #}\n+{%- macro deserializer_field(field, loop) %}\n+{% if field|is_pod or field|is_enum %}\n+\t{%- set field_size = (field|bit_width|int / 8)|int %}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }}\n+\t\t{%- if field|is_pod %}\n+\t\tret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(readUInt<{{field|name}}>(m));\n+\t\t{%- else %}\n+\t\tret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(readUInt<uint{{field|bit_width}}_t>(m));\n+\t\t{%- endif %}\n+\t{%- if not loop.last %}\n+\t\tm += {{field_size}};\n+\t\tdataSize -= {{field_size}};\n+\t{%- endif %}\n+{% elif field|is_fd %}\n+\t{%- set field_size = 1 %}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }}\n+\t\tret.{{field.mojom_name}}_ = IPADataSerializer<{{field|name}}>::deserialize(m, m + 1, n, n + 1, cs);\n+\t{%- if not loop.last %}\n+\t\tm += {{field_size}};\n+\t\tdataSize -= {{field_size}};\n+\t\tn += ret.{{field.mojom_name}}_.isValid() ? 1 : 0;\n+\t\tfdsSize -= ret.{{field.mojom_name}}_.isValid() ? 1 : 0;\n+\t{%- endif %}\n+{% elif field|is_controls %}\n+\t{%- set field_size = 4 %}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data') }}\n+\t\tsize_t {{field.mojom_name}}Size = readUInt<uint32_t>(m);\n+\t{%- set field_size = '4 + ' + field.mojom_name + 'Size' -%}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }}\n+\t\tif ({{field.mojom_name}}Size > 0)\n+\t\t\tret.{{field.mojom_name}}_ =\n+\t\t\t\tIPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs);\n+\t{%- if not loop.last %}\n+\t\tm += {{field_size}};\n+\t\tdataSize -= {{field_size}};\n+\t{%- endif %}\n+{% elif field|is_plain_struct or field|is_array or field|is_map%}\n+\t{%- set field_size = 4 %}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data') }}\n+\t\tsize_t {{field.mojom_name}}Size = readUInt<uint32_t>(m);\n+\t{%- if field|has_fd %}\n+\t{%- set field_size = 8 %}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name + 'FdsSize', 'data') }}\n+\t\tsize_t {{field.mojom_name}}FdsSize = readUInt<uint32_t>(m + 4);\n+\t\t{{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds') }}\n+\t{%- endif %}\n+\t{%- set field_size = field|has_fd|choose('8 + ', '4 + ') + field.mojom_name + 'Size' -%}\n+\t\t{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }}\n+\t\tret.{{field.mojom_name}}_ =\n+\t{%- if field|has_fd %}\n+\t\t\tIPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs);\n+\t{%- else %}\n+\t\t\tIPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs);\n+\t{%- endif %}\n+\t{%- if not loop.last %}\n+\t\tm += {{field_size}};\n+\t\tdataSize -= {{field_size}};\n+\t{%- if field|has_fd %}\n+\t\tn += {{field.mojom_name}}FdsSize;\n+\t\tfdsSize -= {{field.mojom_name}}FdsSize;\n+\t{%- endif %}\n+\t{%- endif %}\n+{% else %}\n+\t\t/* Unknown deserialization for {{field.mojom_name}}. */\n+{%- endif %}\n+{%- endmacro %}\n+\n+\n+{#\n+ # \\brief Serialize a struct\n+ #\n+ # Generate code for IPADataSerializer specialization, for serializing\n+ # \\a struct. \\a base_controls indicates the default ControlInfoMap\n+ # in the event that the ControlList does not have one.\n+ #}\n+{%- macro serializer(struct, base_controls) %}\n+\tstatic std::tuple<std::vector<uint8_t>, std::vector<int32_t>>\n+\tserialize(const {{ struct.mojom_name }} &data,\n+{%- if struct|needs_control_serializer %}\n+\t\t  ControlSerializer *cs)\n+{%- else %}\n+\t\t  [[maybe_unused]] ControlSerializer *cs = nullptr)\n+{%- endif %}\n+\t{\n+\t\tstd::vector<uint8_t> ret_data;\n+{%- if struct|has_fd %}\n+\t\tstd::vector<int32_t> ret_fds;\n+{%- endif %}\n+{%- for field in struct.fields %}\n+{{serializer_field(field, base_controls, loop)}}\n+{%- endfor %}\n+{% if struct|has_fd %}\n+\t\treturn {ret_data, ret_fds};\n+{%- else %}\n+\t\treturn {ret_data, {}};\n+{%- endif %}\n+\t}\n+{%- endmacro %}\n+\n+\n+{#\n+ # \\brief Deserialize a struct that has fds\n+ #\n+ # Generate code for IPADataSerializer specialization, for deserializing\n+ # \\a struct, in the case that \\a struct has file descriptors.\n+ #}\n+{%- macro deserializer_fd(struct) %}\n+\tstatic {{ struct.mojom_name }}\n+\tdeserialize(std::vector<uint8_t> &data,\n+\t\t    std::vector<int32_t> &fds,\n+{%- if struct|needs_control_serializer %}\n+\t\t    ControlSerializer *cs)\n+{%- else %}\n+\t\t    [[maybe_unused]] ControlSerializer *cs = nullptr)\n+{%- endif %}\n+\t{\n+\t\t{{struct.mojom_name}} ret;\n+\t\tstd::vector<uint8_t>::iterator m = data.begin();\n+\t\tstd::vector<int32_t>::iterator n = fds.begin();\n+\n+\t\tsize_t dataSize = data.size();\n+\t\tsize_t fdsSize = fds.size();\n+{%- for field in struct.fields -%}\n+{{deserializer_field(field, loop)}}\n+{%- endfor %}\n+\t\treturn ret;\n+\t}\n+\n+\tstatic {{ struct.mojom_name }}\n+\tdeserialize(std::vector<uint8_t>::iterator data_it1,\n+\t\t    std::vector<uint8_t>::iterator data_it2,\n+\t\t    std::vector<int32_t>::iterator fds_it1,\n+\t\t    std::vector<int32_t>::iterator fds_it2,\n+{%- if struct|needs_control_serializer %}\n+\t\t    ControlSerializer *cs)\n+{%- else %}\n+\t\t    [[maybe_unused]] ControlSerializer *cs = nullptr)\n+{%- endif %}\n+\t{\n+\t\tstd::vector<uint8_t> data(data_it1, data_it2);\n+\t\tstd::vector<int32_t> fds(fds_it1, fds_it2);\n+\t\treturn IPADataSerializer<{{struct.mojom_name}}>::deserialize(data, fds, cs);\n+\t}\n+{%- endmacro %}\n+\n+\n+{#\n+ # \\brief Deserialize a struct that has no fds\n+ #\n+ # Generate code for IPADataSerializer specialization, for deserializing\n+ # \\a struct, in the case that \\a struct does not have file descriptors.\n+ #}\n+{%- macro deserializer_no_fd(struct) %}\n+\tstatic {{ struct.mojom_name }}\n+\tdeserialize(std::vector<uint8_t> &data,\n+{%- if struct|needs_control_serializer %}\n+\t\t    ControlSerializer *cs)\n+{%- else %}\n+\t\t    [[maybe_unused]] ControlSerializer *cs = nullptr)\n+{%- endif %}\n+\t{\n+\t\t{{struct.mojom_name}} ret;\n+\t\tstd::vector<uint8_t>::iterator m = data.begin();\n+\n+\t\tsize_t dataSize = data.size();\n+{%- for field in struct.fields -%}\n+{{deserializer_field(field, loop)}}\n+{%- endfor %}\n+\t\treturn ret;\n+\t}\n+\n+\tstatic {{ struct.mojom_name }}\n+\tdeserialize(std::vector<uint8_t>::iterator data_it1,\n+\t\t    std::vector<uint8_t>::iterator data_it2,\n+{%- if struct|needs_control_serializer %}\n+\t\t    ControlSerializer *cs)\n+{%- else %}\n+\t\t    [[maybe_unused]] ControlSerializer *cs = nullptr)\n+{%- endif %}\n+\t{\n+\t\tstd::vector<uint8_t> data(data_it1, data_it2);\n+\t\treturn IPADataSerializer<{{struct.mojom_name}}>::deserialize(data, cs);\n+\t}\n+{%- endmacro %}\ndiff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py\nnew file mode 100644\nindex 00000000..6a65dbc2\n--- /dev/null\n+++ b/utils/ipc/generators/mojom_libcamera_generator.py\n@@ -0,0 +1,403 @@\n+'''Generates libcamera files from a mojom.Module.'''\n+\n+import argparse\n+import ast\n+import datetime\n+import contextlib\n+import os\n+import re\n+import shutil\n+import sys\n+import tempfile\n+\n+from jinja2 import contextfilter\n+\n+import mojom.fileutil as fileutil\n+import mojom.generate.generator as generator\n+import mojom.generate.module as mojom\n+from mojom.generate.template_expander import UseJinja\n+\n+\n+GENERATOR_PREFIX = 'libcamera'\n+\n+_kind_to_cpp_type = {\n+    mojom.BOOL:   'bool',\n+    mojom.INT8:   'int8_t',\n+    mojom.UINT8:  'uint8_t',\n+    mojom.INT16:  'int16_t',\n+    mojom.UINT16: 'uint16_t',\n+    mojom.INT32:  'int32_t',\n+    mojom.UINT32: 'uint32_t',\n+    mojom.FLOAT:  'float',\n+    mojom.INT64:  'int64_t',\n+    mojom.UINT64: 'uint64_t',\n+    mojom.DOUBLE: 'double',\n+}\n+\n+_bit_widths = {\n+    mojom.BOOL:   '8',\n+    mojom.INT8:   '8',\n+    mojom.UINT8:  '8',\n+    mojom.INT16:  '16',\n+    mojom.UINT16: '16',\n+    mojom.INT32:  '32',\n+    mojom.UINT32: '32',\n+    mojom.FLOAT:  '32',\n+    mojom.INT64:  '64',\n+    mojom.UINT64: '64',\n+    mojom.DOUBLE: '64',\n+}\n+\n+def ModuleName(path):\n+    return path.split('/')[-1].split('.')[0]\n+\n+def ModuleClassName(module):\n+    s = re.sub(r'^IPA', '',  module.interfaces[0].mojom_name)\n+    return re.sub(r'Interface$', '', s)\n+\n+def UpperCamelCase(name):\n+    return ''.join([x.capitalize() for x in generator.SplitCamelCase(name)])\n+\n+def CamelCase(name):\n+    uccc = UpperCamelCase(name)\n+    return uccc[0].lower() + uccc[1:]\n+\n+def ConstantStyle(name):\n+    return generator.ToUpperSnakeCase(name)\n+\n+def Choose(cond, t, f):\n+    return t if cond else f\n+\n+def CommaSep(l):\n+    return ', '.join([m for m in l])\n+\n+def ParamsCommaSep(l):\n+    return ', '.join([m.mojom_name for m in l])\n+\n+def GetDefaultValue(element):\n+    if element.default is not None:\n+        return element.default\n+    if type(element.kind) == mojom.Kind:\n+        return '0'\n+    if mojom.IsEnumKind(element.kind):\n+        return f'static_cast<{element.kind.mojom_name}>(0)'\n+    if (isinstance(element.kind, mojom.Struct) and\n+       element.kind.mojom_name == 'FileDescriptor'):\n+        return '-1'\n+    return ''\n+\n+def WithDefaultValues(element):\n+    return [x for x in element if HasDefaultValue(x)]\n+\n+def WithFds(element):\n+    return [x for x in element if HasFd(x)]\n+\n+def HasDefaultValue(element):\n+    return GetDefaultValue(element) != ''\n+\n+def HasDefaultFields(element):\n+    return True in [HasDefaultValue(x) for x in element.fields]\n+\n+def GetAllTypes(element):\n+    if mojom.IsArrayKind(element):\n+        return GetAllTypes(element.kind)\n+    if mojom.IsMapKind(element):\n+        return GetAllTypes(element.key_kind) + GetAllTypes(element.value_kind)\n+    if isinstance(element, mojom.Parameter):\n+        return GetAllTypes(element.kind)\n+    if mojom.IsEnumKind(element):\n+        return [element.mojom_name]\n+    if not mojom.IsStructKind(element):\n+        return [element.spec]\n+    if len(element.fields) == 0:\n+        return [element.mojom_name]\n+    ret = [GetAllTypes(x.kind) for x in element.fields]\n+    ret = [x for sublist in ret for x in sublist]\n+    return list(set(ret))\n+\n+def GetAllAttrs(element):\n+    if mojom.IsArrayKind(element):\n+        return GetAllAttrs(element.kind)\n+    if mojom.IsMapKind(element):\n+        return {**GetAllAttrs(element.key_kind), **GetAllAttrs(element.value_kind)}\n+    if isinstance(element, mojom.Parameter):\n+        return GetAllAttrs(element.kind)\n+    if mojom.IsEnumKind(element):\n+        return element.attributes if element.attributes is not None else {}\n+    if mojom.IsStructKind(element) and len(element.fields) == 0:\n+        return element.attributes if element.attributes is not None else {}\n+    if not mojom.IsStructKind(element):\n+        return {}\n+    attrs = [(x.attributes) for x in element.fields]\n+    ret = {}\n+    for d in attrs:\n+        if d is not None:\n+            ret = {**ret, **d}\n+    return ret\n+\n+def NeedsControlSerializer(element):\n+    types = GetAllTypes(element)\n+    return \"ControlList\" in types or \"ControlInfoMap\" in types\n+\n+def HasFd(element):\n+    attrs = GetAllAttrs(element)\n+    if isinstance(element, mojom.Kind):\n+        types = GetAllTypes(element)\n+    else:\n+        types = GetAllTypes(element.kind)\n+    return \"FileDescriptor\" in types or (attrs is not None and \"hasFd\" in attrs)\n+\n+def MethodInputHasFd(method):\n+    if len([x for x in method.parameters if HasFd(x)]) > 0:\n+        return True\n+    return False\n+\n+def MethodOutputHasFd(method):\n+    if (MethodReturnValue(method) != 'void' or\n+        method.response_parameters is None):\n+        return False\n+    if len([x for x in method.parameters if HasFd(x)]) > 0:\n+        return True\n+    return False\n+\n+def MethodParamInputs(method):\n+    return method.parameters\n+\n+def MethodParamOutputs(method):\n+    if (MethodReturnValue(method) != 'void' or\n+        method.response_parameters is None):\n+        return []\n+    return method.response_parameters\n+\n+def MethodParamNames(method):\n+    params = []\n+    for param in method.parameters:\n+        params.append(param.mojom_name)\n+    if MethodReturnValue(method) == 'void':\n+        if method.response_parameters is None:\n+            return params\n+        for param in method.response_parameters:\n+            params.append(param.mojom_name)\n+    return params\n+\n+def MethodParameters(method):\n+    params = []\n+    for param in method.parameters:\n+        params.append('const %s %s%s' % (GetNameForElement(param),\n+                                         ('&' if not IsPod(param) else ''),\n+                                         param.mojom_name))\n+    if MethodReturnValue(method) == 'void':\n+        if method.response_parameters is None:\n+            return params\n+        for param in method.response_parameters:\n+            params.append(f'{GetNameForElement(param)} *{param.mojom_name}')\n+    return params\n+\n+def MethodReturnValue(method):\n+    if method.response_parameters is None:\n+        return 'void'\n+    if len(method.response_parameters) == 1 and IsPod(method.response_parameters[0]):\n+        return GetNameForElement(method.response_parameters[0])\n+    return 'void'\n+\n+def IsAsync(method):\n+    # callbacks are always async\n+    if re.match(\"^IPA.*CallbackInterface$\", method.interface.mojom_name):\n+        return True\n+    elif re.match(\"^IPA.*Interface$\", method.interface.mojom_name):\n+        if method.attributes is None:\n+            return False\n+        elif 'async' in method.attributes and method.attributes['async']:\n+            return True\n+    return False\n+\n+def IsArray(element):\n+    return mojom.IsArrayKind(element.kind)\n+\n+def IsControls(element):\n+    return mojom.IsStructKind(element.kind) and (element.kind.mojom_name == \"ControlList\" or\n+                                                 element.kind.mojom_name == \"ControlInfoMap\")\n+\n+def IsEnum(element):\n+    return mojom.IsEnumKind(element.kind)\n+\n+def IsFd(element):\n+    return mojom.IsStructKind(element.kind) and element.kind.mojom_name == \"FileDescriptor\"\n+\n+def IsMap(element):\n+    return mojom.IsMapKind(element.kind)\n+\n+def IsPlainStruct(element):\n+    return mojom.IsStructKind(element.kind) and not IsControls(element) and not IsFd(element)\n+\n+def IsPod(element):\n+    return element.kind in _kind_to_cpp_type\n+\n+def BitWidth(element):\n+    if element.kind in _bit_widths:\n+        return _bit_widths[element.kind]\n+    if mojom.IsEnumKind(element.kind):\n+        return '32'\n+    return ''\n+\n+def GetNameForElement(element):\n+    if (mojom.IsEnumKind(element) or\n+        mojom.IsInterfaceKind(element) or\n+        mojom.IsStructKind(element)):\n+        return element.mojom_name\n+    if (mojom.IsArrayKind(element)):\n+        elem_name = GetNameForElement(element.kind)\n+        return f'std::vector<{elem_name}>'\n+    if (mojom.IsMapKind(element)):\n+        key_name = GetNameForElement(element.key_kind)\n+        value_name = GetNameForElement(element.value_kind)\n+        return f'std::map<{key_name}, {value_name}>'\n+    if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)):\n+        if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)):\n+            return GetNameForElement(element.kind)\n+        if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type):\n+            return _kind_to_cpp_type[element.kind]\n+        return element.kind.mojom_name\n+    if isinstance(element,  mojom.EnumValue):\n+        return (GetNameForElement(element.enum) + '.' +\n+                ConstantStyle(element.name))\n+    if isinstance(element, (mojom.NamedValue,\n+                            mojom.Constant,\n+                            mojom.EnumField)):\n+        return ConstantStyle(element.name)\n+    if (hasattr(element, '__hash__') and element in _kind_to_cpp_type):\n+        return _kind_to_cpp_type[element]\n+    if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type):\n+        return _kind_to_cpp_type[element.kind]\n+    if (hasattr(element, 'spec')):\n+        return element.spec.split(':')[-1]\n+    if (mojom.IsInterfaceRequestKind(element) or\n+        mojom.IsAssociatedKind(element) or\n+        mojom.IsPendingRemoteKind(element) or\n+        mojom.IsPendingReceiverKind(element) or\n+        mojom.IsUnionKind(element)):\n+        raise Exception('Unsupported element: %s' % element)\n+    raise Exception('Unexpected element: %s' % element)\n+\n+\n+class Generator(generator.Generator):\n+\n+    @staticmethod\n+    def GetTemplatePrefix():\n+        return 'libcamera_templates'\n+\n+    def GetFilters(self):\n+        libcamera_filters = {\n+            'all_types': GetAllTypes,\n+            'bit_width': BitWidth,\n+            'choose': Choose,\n+            'comma_sep': CommaSep,\n+            'default_value': GetDefaultValue,\n+            'has_default_fields': HasDefaultFields,\n+            'has_fd': HasFd,\n+            'is_async': IsAsync,\n+            'is_array': IsArray,\n+            'is_controls': IsControls,\n+            'is_enum': IsEnum,\n+            'is_fd': IsFd,\n+            'is_map': IsMap,\n+            'is_plain_struct': IsPlainStruct,\n+            'is_pod': IsPod,\n+            'method_input_has_fd': MethodInputHasFd,\n+            'method_output_has_fd': MethodOutputHasFd,\n+            'method_param_names': MethodParamNames,\n+            'method_param_inputs': MethodParamInputs,\n+            'method_param_outputs': MethodParamOutputs,\n+            'method_parameters': MethodParameters,\n+            'method_return_value': MethodReturnValue,\n+            'name': GetNameForElement,\n+            'needs_control_serializer': NeedsControlSerializer,\n+            'params_comma_sep': ParamsCommaSep,\n+            'with_default_values': WithDefaultValues,\n+            'with_fds': WithFds,\n+        }\n+        return libcamera_filters\n+\n+    def _GetJinjaExports(self):\n+        return {\n+            'base_controls': '%sControls' % ModuleClassName(self.module),\n+            'cmd_enum_name': '%sCMD' % ModuleClassName(self.module),\n+            'enums': self.module.enums,\n+            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,\n+            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,\n+            'has_namespace': self.module.namespace is not None,\n+            'imports': self.module.imports,\n+            'interface_cb': self.module.interfaces[1],\n+            'interface_main': self.module.interfaces[0],\n+            'interface_name': 'IPA%sInterface' % ModuleClassName(self.module),\n+            'ipc_name': 'IPAIPCUnixSocket',\n+            'kinds': self.module.kinds,\n+            'module': self.module,\n+            'module_name': ModuleName(self.module.path),\n+            'module_class_name': ModuleClassName(self.module),\n+            'namespace': self.module.namespace,\n+            'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module),\n+            'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module),\n+            'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0],\n+            'year': datetime.datetime.now().year,\n+        }\n+\n+\n+    @UseJinja('module_generated.h.tmpl')\n+    def _GenerateDataHeader(self):\n+        return self._GetJinjaExports()\n+\n+    @UseJinja('module_serializer.h.tmpl')\n+    def _GenerateSerializer(self):\n+        return self._GetJinjaExports()\n+\n+    @UseJinja('module_ipa_proxy.cpp.tmpl')\n+    def _GenerateProxyCpp(self):\n+        return self._GetJinjaExports()\n+\n+    @UseJinja('module_ipa_proxy.h.tmpl')\n+    def _GenerateProxyHeader(self):\n+        return self._GetJinjaExports()\n+\n+    @UseJinja('module_ipa_proxy_worker.cpp.tmpl')\n+    def _GenerateProxyWorker(self):\n+        return self._GetJinjaExports()\n+\n+    def GenerateFiles(self, unparsed_args):\n+        parser = argparse.ArgumentParser()\n+        parser.add_argument('--libcamera_generate_header',       action='store_true')\n+        parser.add_argument('--libcamera_generate_serializer',   action='store_true')\n+        parser.add_argument('--libcamera_generate_proxy_cpp',    action='store_true')\n+        parser.add_argument('--libcamera_generate_proxy_h',      action='store_true')\n+        parser.add_argument('--libcamera_generate_proxy_worker', action='store_true')\n+        parser.add_argument('--libcamera_output_path')\n+        args = parser.parse_args(unparsed_args)\n+\n+        fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path))\n+\n+        module_name = ModuleName(self.module.path)\n+\n+        if args.libcamera_generate_header:\n+            self.Write(self._GenerateDataHeader(),\n+                       args.libcamera_output_path)\n+                       #f'{module_name}_generated.h')\n+\n+        if args.libcamera_generate_serializer:\n+            self.Write(self._GenerateSerializer(),\n+                       args.libcamera_output_path)\n+                       #f'{module_name}_serializer.h')\n+\n+        if args.libcamera_generate_proxy_cpp:\n+            self.Write(self._GenerateProxyCpp(),\n+                       args.libcamera_output_path)\n+                       #f'ipa_proxy_{module_name}.cpp')\n+\n+        if args.libcamera_generate_proxy_h:\n+            self.Write(self._GenerateProxyHeader(),\n+                       args.libcamera_output_path)\n+                       #f'ipa_proxy_{module_name}.h')\n+\n+        if args.libcamera_generate_proxy_worker:\n+            self.Write(self._GenerateProxyWorker(),\n+                       args.libcamera_output_path)\n+                       #f'ipa_proxy_{module_name}_worker.cpp')\n","prefixes":["libcamera-devel","03/23"]}