From patchwork Tue Sep 22 13:35:07 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 9712 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 38DE7C3B5B for ; Tue, 22 Sep 2020 13:36:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 04F9E62FE5; Tue, 22 Sep 2020 15:36:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rQoNoCsd"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 024C260576 for ; Tue, 22 Sep 2020 15:36:50 +0200 (CEST) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 50979555; Tue, 22 Sep 2020 15:36:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1600781805; bh=0WSje7+HjwC+hlnImC33hqqdy0mnTuEZV8gFRViz/s0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rQoNoCsdbOoJkpITjZgql3DnwLXkxpXsXgoSdNUJQcSnB/w3EiXwJAFUDtquMVrjY 1efkJWbXG5QL7HaI12U1GGV6MaGQV2b9igzzjS751O5VYMW9P5BQnWAyME91qYFCc2 YszQgQkLDMG7nxoxFXlUzpNNqF1jwg7NzHlMslVs= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Tue, 22 Sep 2020 22:35:07 +0900 Message-Id: <20200922133537.258098-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20200922133537.258098-1-paul.elder@ideasonboard.com> References: <20200922133537.258098-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 08/38] utils: ipc: add templates for code generation for IPC mechanism X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add templates to mojo to generate code for the IPC mechanism. These templates generate: - module header - module serializer - IPA proxy cpp, header, and worker Given an input data definition mojom file for a pipeline. Signed-off-by: Paul Elder --- Changes in v2: - mandate the main and callback interfaces, and init(), start(), stop() and their parameters - fix returning single pod value from IPC-called function - add licenses - improve auto-generated message - other fixes related to serdes --- .../module_generated.h.tmpl | 96 ++++ .../module_ipa_proxy.cpp.tmpl | 222 +++++++++ .../module_ipa_proxy.h.tmpl | 107 +++++ .../module_ipa_proxy_worker.cpp.tmpl | 178 +++++++ .../module_serializer.h.tmpl | 44 ++ .../libcamera_templates/proxy_functions.tmpl | 185 +++++++ .../libcamera_templates/serializer.tmpl | 268 +++++++++++ .../generators/mojom_libcamera_generator.py | 452 ++++++++++++++++++ 8 files changed, 1552 insertions(+) create mode 100644 utils/ipc/generators/libcamera_templates/module_generated.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py diff --git a/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl b/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl new file mode 100644 index 00000000..ee8ff3af --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_generated.h.tmpl @@ -0,0 +1,96 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{ year }}, Google Inc. + * + * {{ module_name }}_generated.h - Image Processing Algorithm interface for {{ module_name }} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__ +#define __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__ + +#include + +#include + +{% if has_map %}#include {% endif %} +{% if has_array %}#include {% endif %} + +namespace libcamera { + +enum {{ cmd_enum_name }} { + CMD_EXIT = 0, +{%- for method in interface_main.methods %} + CMD_{{ method.mojom_name|upper }} = {{loop.index}}, +{%- endfor %} +{%- set count = interface_main.methods|length -%} +{%- for method in interface_cb.methods %} + CMD_{{ method.mojom_name|upper }} = {{loop.index + count}}, +{%- endfor %} +}; + +{% for enum in enums %} +enum {{ enum.mojom_name }} { +{%- for field in enum.fields %} + {{ field.mojom_name }} = {{ field.numeric_value + 1 }}, +{%- endfor %} +}; +{% endfor %} + +{%- for struct in structs_nonempty %} +struct {{ struct.mojom_name }} +{ +public: + {{ struct.mojom_name }}() {%- if struct|has_default_fields %} :{% endif %} +{%- for field in struct.fields|with_default_values -%} +{{" " if loop.first}}{{ field.mojom_name}}_({{ field|default_value }}){{ ", " if not loop.last }} +{%- endfor %} {} + ~{{ struct.mojom_name }}() {} + + {{ struct.mojom_name }}( +{%- for field in struct.fields -%} +{{ field|name }} {{ field.mojom_name }}{{ ", " if not loop.last }} +{%- endfor -%} +) : +{%- for field in struct.fields -%} +{{" " if loop.first}}{{ field.mojom_name}}_({{ field.mojom_name }}){{ ", " if not loop.last }} +{%- endfor %} {} +{% for field in struct.fields %} + {{ field|name }} {{ field.mojom_name }}_; +{%- endfor %} +}; +{% endfor %} + +{#- +What to do about the #includes? Should we forward-declare, or +require {{module_name}}.h to include the required headers? +For now I'm going for the latter, coz this header essentially +extends {{module_name}}.h. +#} +class {{ interface_name }} : public IPAInterface +{ +public: + virtual ~{{interface_name}}() {}; +{% for method in interface_main.methods %} + virtual {{method|method_return_value}} {{method.mojom_name}}( +{%- for param in method|method_parameters %} + {{param}}{{- "," if not loop.last}} +{%- endfor -%} +) = 0; +{% endfor %} + +{%- for method in interface_cb.methods %} + Signal< +{%- for param in method.parameters -%} + const {{param|name}}{{" &" if not param|is_pod}} + {{- ", " if not loop.last }} +{%- endfor -%} +> {{method.mojom_name}}; +{% endfor -%} +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_IPA_INTERFACE_{{ module_name|upper }}_GENERATED_H__ */ diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl new file mode 100644 index 00000000..736d224f --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl @@ -0,0 +1,222 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{ year }}, Google Inc. + * + * ipa_proxy_{{ module_name }}.cpp - Image Processing Algorithm proxy for {{ module_name }} + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include +#include +#include +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_ipc.h" +#include "libcamera/internal/ipa_ipc_unixsocket.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/ipa_proxy.h" +#include "libcamera/internal/ipc_unixsocket.h" +#include "libcamera/internal/log.h" +#include "libcamera/internal/process.h" +#include "libcamera/internal/thread.h" + +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPAProxy) + +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) + : IPAProxy(ipam), running_(false), + isolate_(isolate) +{ + LOG(IPAProxy, Debug) + << "initializing dummy proxy: loading IPA from " + << ipam->path(); + + if (isolate_) { + const std::string proxy_worker_path = resolvePath("ipa_proxy_{{module_name}}"); + if (proxy_worker_path.empty()) { + LOG(IPAProxy, Error) + << "Failed to get proxy worker path"; + return; + } + + ipc_ = std::make_unique(ipam->path().c_str(), proxy_worker_path.c_str()); + if (!ipc_->isValid()) { + LOG(IPAProxy, Error) << "Failed to create IPAIPC"; + return; + } + +{% if interface_cb.methods|length > 0 %} + ipc_->recvIPC.connect(this, &{{proxy_name}}::recvIPC); +{% endif %} + + valid_ = true; + return; + } + + if (!ipam->load()) + return; + + IPAInterface *ipai = ipam->createInterface(); + if (!ipai) { + LOG(IPAProxy, Error) + << "Failed to create IPA context for " << ipam->path(); + return; + } + + ipa_ = std::unique_ptr<{{interface_name}}>(dynamic_cast<{{interface_name}} *>(ipai)); + proxy_.setIPA(ipa_.get()); + +{% for method in interface_cb.methods %} + ipa_->{{method.mojom_name}}.connect(this, &{{proxy_name}}::{{method.mojom_name}}Thread); +{%- endfor %} + + valid_ = true; +} + +{{proxy_name}}::~{{proxy_name}}() +{ + if (isolate_) + ipc_->sendAsync(CMD_EXIT, {}, {}); +} + +{% if interface_cb.methods|length > 0 %} +void {{proxy_name}}::recvIPC(std::vector &data, std::vector &fds) +{ + uint32_t cmd = (data[0] & 0xff) | + ((data[1] & 0xff) << 8) | + ((data[2] & 0xff) << 16) | + ((data[3] & 0xff) << 24); + + /* Need to skip another 4 bytes for the sequence number. */ + std::vector vec(data.begin() + 8, data.end()); + switch (cmd) { +{%- for method in interface_cb.methods %} + case CMD_{{method.mojom_name|upper}}: { + {{method.mojom_name}}IPC(vec, fds); + break; + } +{%- endfor %} + } +} +{%- endif %} + +{% for method in interface_main.methods %} +{{proxy_funcs.func_sig(proxy_name, method)}} +{ + if (isolate_) + {{"return " if method|method_return_value != "void"}}{{method.mojom_name}}IPC( +{%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} +{%- endfor -%} +); + else + {{"return " if method|method_return_value != "void"}}{{method.mojom_name}}Thread( +{%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} +{%- endfor -%} +); +} + +{{proxy_funcs.func_sig(proxy_name, method, "Thread")}} +{ +{%- if method.mojom_name == "init" %} + {{proxy_funcs.init_thread_body()}} +{% elif method.mojom_name == "start" %} + {{proxy_funcs.start_thread_body()}} +{% elif method.mojom_name == "stop" %} + {{proxy_funcs.stop_thread_body()}} +{% elif not method|is_async %} + ipa_->{{method.mojom_name}}( + {%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} + {%- endfor -%} +); +{% elif method|is_async %} + proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued, + {%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} + {%- endfor -%} +); +{%- endif %} +} + +{{proxy_funcs.func_sig(proxy_name, method, "IPC")}} +{ +{%- set has_input = true if method|method_param_inputs|length > 0 %} +{%- set has_output = true if method|method_param_outputs|length > 0 or method|method_return_value != "void" %} +{% if has_input %} + std::vector _ipcInputBuf; +{%- if method|method_input_has_fd %} + std::vector _ipcInputFds; +{%- endif %} +{%- endif %} +{%- if has_output %} + std::vector _ipcOutputBuf; +{%- if method|method_output_has_fd %} + std::vector _ipcOutputFds; +{%- endif %} +{%- endif %} + +{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf', '_ipcInputFds', base_controls)}} + +{%- set input_buf = "_ipcInputBuf" if has_input else "{}" %} +{%- set fds_buf = "_ipcInputFds" if method|method_input_has_fd else "{}" %} +{%- set cmd = "CMD_" + method.mojom_name|upper %} +{% if method|is_async %} + int ret = ipc_->sendAsync({{cmd}}, {{input_buf}}, {{fds_buf}}); +{%- else %} + int ret = ipc_->sendSync({{cmd}}, {{input_buf}}, {{fds_buf}} +{{- ", &_ipcOutputBuf" if has_output -}} +{{- ", &_ipcOutputFds" if has_output and method|method_output_has_fd -}} +); +{%- endif %} + if (ret < 0) { + LOG(IPAProxy, Error) << "Failed to call {{method.mojom_name}}"; +{%- if method|method_return_value != "void" %} + return static_cast<{{method|method_return_value}}>(ret); +{%- else %} + return; +{%- endif %} + } + LOG(IPAProxy, Debug) << "Done calling {{method.mojom_name}}"; +{% if method|method_return_value != "void" %} + return IPADataSerializer<{{method.response_parameters|first|name}}>::deserialize(_ipcOutputBuf, 0); +{% elif method|method_param_outputs|length > 0 %} +{{proxy_funcs.deserialize_call(method|method_param_outputs, '_ipcOutputBuf', '_ipcOutputFds')}} +{% endif -%} +} + +{% endfor %} + + +{% for method in interface_cb.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "Thread")}} +{ + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); +} + +void {{proxy_name}}::{{method.mojom_name}}IPC( + std::vector &data, + [[maybe_unused]] std::vector &fds) +{ +{%- for param in method.parameters %} + {{param|name}} {{param.mojom_name}}; +{%- endfor %} +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false)}} + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); +} +{% endfor %} + +} /* namespace libcamera */ diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl new file mode 100644 index 00000000..7d6212af --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl @@ -0,0 +1,107 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{ year }}, Google Inc. + * + * ipa_proxy_{{ module_name }}.h - Image Processing Algorithm proxy for {{ module_name }} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__ +#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__ + +#include +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_ipc.h" +#include "libcamera/internal/ipa_ipc_unixsocket.h" +#include "libcamera/internal/ipa_proxy.h" +#include "libcamera/internal/ipc_unixsocket.h" +#include "libcamera/internal/thread.h" + +namespace libcamera { + +class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object +{ +public: + {{proxy_name}}(IPAModule *ipam, bool isolate); + ~{{proxy_name}}(); + +{% for method in interface_main.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "", false, true)|indent(8, true)}}; +{% endfor %} + +{%- for method in interface_cb.methods %} + Signal< +{%- for param in method.parameters -%} + const {{param|name}}{{" &" if not param|is_pod}} + {{- ", " if not loop.last }} +{%- endfor -%} +> {{method.mojom_name}}; +{% endfor %} + +private: + void recvIPC(std::vector &data, std::vector &fds); + +{% for method in interface_main.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "Thread", false)|indent(8, true)}}; +{{proxy_funcs.func_sig(proxy_name, method, "IPC", false)|indent(8, true)}}; +{% endfor %} +{% for method in interface_cb.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "Thread", false)|indent(8, true)}}; + void {{method.mojom_name}}IPC( + std::vector &data, + std::vector &fds); +{% endfor %} + + /* Helper class to invoke async functions in another thread. */ + class ThreadProxy : public Object + { + public: + void setIPA({{interface_name}} *ipa) + { + ipa_ = ipa; + } + + int start() + { + return ipa_->start(); + } + + void stop() + { + ipa_->stop(); + } +{% for method in interface_main.methods %} +{%- if method|is_async %} + {{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}} + { + ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}); + } +{%- endif %} +{%- endfor %} + + private: + {{interface_name}} *ipa_; + }; + + bool running_; + Thread thread_; + ThreadProxy proxy_; + std::unique_ptr<{{interface_name}}> ipa_; + + const bool isolate_; + + std::unique_ptr ipc_; + + ControlSerializer controlSerializer_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_PROXY_{{ module_name|upper }}_H__ */ diff --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 new file mode 100644 index 00000000..2d6e71b8 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl @@ -0,0 +1,178 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{ year }}, Google Inc. + * + * ipa_proxy_{{ module_name }}_worker.cpp - Image Processing Algorithm proxy worker for {{ module_name }} + * + * This file is auto-generated. Do not edit. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipa_ipc_unixsocket.h" +#include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/ipa_proxy.h" +#include "libcamera/internal/ipc_unixsocket.h" +#include "libcamera/internal/log.h" +#include "libcamera/internal/thread.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY({{proxy_name}}Worker) + +struct CallData { + IPCUnixSocket::Payload *response; + bool done; +}; + +{{interface_name}} *ipa_; +IPCUnixSocket socket_; +std::map callData_; + +ControlSerializer controlSerializer_; + +bool exit_ = false; + +void readyRead(IPCUnixSocket *socket) +{ + IPCUnixSocket::Payload _message, _response; + int _ret = socket->receive(&_message); + if (_ret) { + LOG({{proxy_name}}Worker, Error) + << "Receive message failed" << _ret; + return; + } + + uint32_t _cmd, _seq; + std::tie(_cmd, _seq) = readHeader(_message); + eraseHeader(_message); + + switch (_cmd) { + case CMD_EXIT: { + exit_ = true; + break; + } + +{% for method in interface_main.methods %} + case CMD_{{method.mojom_name|upper}}: { + {{proxy_funcs.deserialize_call(method|method_param_inputs, '_message.data', '_message.fds', false, true)|indent(8, true)}} +{% for param in method|method_param_outputs %} + {{param|name}} {{param.mojom_name}}; +{% endfor %} +{%- if method|method_return_value != "void" %} + {{method|method_return_value}} _callRet = ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}); +{%- else %} + ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}} +{{- ", " if method|method_param_outputs|params_comma_sep -}} +{%- for param in method|method_param_outputs -%} +&{{param.mojom_name}}{{", " if not loop.last}} +{%- endfor -%} +); +{%- endif %} +{% if not method|is_async %} + writeHeader(_response, _cmd, _seq); +{%- if method|method_return_value != "void" %} + std::vector _callRetBuf; + std::tie(_callRetBuf, std::ignore) = + IPADataSerializer<{{method|method_return_value}}>::serialize(_callRet); + _response.data.insert(_response.data.end(), _callRetBuf.begin(), _callRetBuf.end()); +{%- else %} + {{proxy_funcs.serialize_call(method|method_param_outputs, "_response.data", "_response.fds", base_controls)|indent(8, true)}} +{%- endif %} + int _ret = socket_.send(_response); + if (_ret < 0) { + LOG({{proxy_name}}Worker, Error) + << "Reply to {{method.mojom_name}}() failed" << _ret; + } + LOG({{proxy_name}}Worker, Debug) << "Done replying to {{method.mojom_name}}()"; +{%- endif %} + break; + } +{% endfor %} + } +} + +{% for method in interface_cb.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} +{ + IPCUnixSocket::Payload _message; + + writeHeader(_message, CMD_{{method.mojom_name|upper}}, 0); + {{proxy_funcs.serialize_call(method|method_param_inputs, "_message.data", "_message.fds", base_controls)}} + + socket_.send(_message); + + LOG({{proxy_name}}Worker, Debug) << "{{method.mojom_name}} done"; +} +{%- endfor %} + +int main(int argc, char **argv) +{ + /* Uncomment this for debugging. */ +#if 0 + std::string logPath = "/tmp/libcamera.worker." + + std::to_string(getpid()) + ".log"; + logSetFile(logPath.c_str()); +#endif + + if (argc < 3) { + LOG({{proxy_name}}Worker, Debug) + << "Tried to start worker with no args"; + return EXIT_FAILURE; + } + + int fd = std::stoi(argv[2]); + LOG({{proxy_name}}Worker, Debug) + << "Starting worker for IPA module " << argv[1] + << " with IPC fd = " << fd; + + std::unique_ptr ipam = std::make_unique(argv[1]); + if (!ipam->isValid() || !ipam->load()) { + LOG({{proxy_name}}Worker, Error) + << "IPAModule " << argv[1] << " should be valid but isn't"; + return EXIT_FAILURE; + } + + if (socket_.bind(fd) < 0) { + LOG({{proxy_name}}Worker, Error) << "IPC socket binding failed"; + return EXIT_FAILURE; + } + socket_.readyRead.connect(&readyRead); + + ipa_ = dynamic_cast<{{interface_name}} *>(ipam->createInterface()); + if (!ipa_) { + LOG({{proxy_name}}Worker, Error) << "Failed to create IPA interface instance"; + return EXIT_FAILURE; + } +{% for method in interface_cb.methods %} + ipa_->{{method.mojom_name}}.connect(&{{method.mojom_name}}); +{%- endfor %} + + LOG({{proxy_name}}Worker, Debug) << "Proxy worker successfully started"; + + /* \todo upgrade listening loop */ + EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + while (!exit_) + dispatcher->processEvents(); + + delete ipa_; + socket_.close(); + + return 0; +} diff --git a/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl new file mode 100644 index 00000000..1658edd9 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_serializer.h.tmpl @@ -0,0 +1,44 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{%- import "serializer.tmpl" as serializer -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{ year }}, Google Inc. + * + * {{ module_name }}_serializer.h - Image Processing Algorithm data serializer for {{ module_name }} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__ +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__ + +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" + +#include +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPADataSerializer) +{% for struct in structs_nonempty %} +template<> +class IPADataSerializer<{{ struct.mojom_name }}> +{ +public: +{{- serializer.serializer(struct, base_controls) }} +{%- if struct|has_fd %} +{{ serializer.deserializer_fd(struct) }} +{%- else %} +{{ serializer.deserializer_no_fd(struct) }} +{%- endif %} +}; +{% endfor %} + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{ module_name|upper }}_H__ */ diff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl new file mode 100644 index 00000000..e1f5a20c --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl @@ -0,0 +1,185 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{# + # \brief Generate fuction prototype + # + # \param class Class name + # \param method mojom Method object + # \param suffix Suffix to append to \a method function name + # \param need_class_name True to generate class name with function + # \param override True to generate override tag after the function prototype + #} +{%- macro func_sig(class, method, suffix, need_class_name = true, override = false) -%} +{{method|method_return_value}} {{ class + "::" if need_class_name }}{{method.mojom_name}}{{suffix}}( +{%- for param in method|method_parameters %} + {{param}}{{- "," if not loop.last}} +{%- endfor -%} +){{" override" if override}} +{%- endmacro -%} + +{# + # \brief Generate function body for IPA init() function for thread + #} +{%- macro init_thread_body() -%} + int ret = ipa_->init(settings); + if (ret) + return ret; + + proxy_.moveToThread(&thread_); + + return 0; +{%- endmacro -%} + +{# + # \brief Generate function body for IPA start() function for thread + #} +{%- macro start_thread_body() -%} + running_ = true; + thread_.start(); + + return proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking); +{%- endmacro -%} + +{# + # \brief Generate function body for IPA stop() function for thread + #} +{%- macro stop_thread_body() -%} + if (!running_) + return; + + running_ = false; + + proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking); + + thread_.exit(); + thread_.wait(); +{%- endmacro -%} + + +{# + # \brief Serialize multiple objects into data buffer and fd vector + # + # Generate code to serialize multiple objects, as specified in \a params + # (which are the parameters to some function), into \a buf data buffer and + # \a fds fd vector. + # This code is meant to be used by the proxy, for serializing prior to IPC calls. + #} +{%- macro serialize_call(params, buf, fds, base_controls) %} +{%- for param in params %} + std::vector {{param.mojom_name}}Buf; +{%- if param|has_fd %} + std::vector {{param.mojom_name}}Fds; + std::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) = +{%- else %} + std::tie({{param.mojom_name}}Buf, std::ignore) = +{%- endif %} +{%- if param|is_controls %} + IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}, {{param.mojom_name}}.infoMap() ? *{{param.mojom_name}}.infoMap() : {{base_controls}} +{%- else %} + IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}} +{%- endif %} +{{- ", &controlSerializer_" if param|needs_control_serializer -}} +); +{%- endfor %} + +{%- if params|length > 1 %} +{%- for param in params %} + appendUInt({{buf}}, {{param.mojom_name}}Buf.size()); +{%- if param|has_fd %} + appendUInt({{buf}}, {{param.mojom_name}}Fds.size()); +{%- endif %} +{%- endfor %} +{%- endif %} + +{%- for param in params %} + {{buf}}.insert({{buf}}.end(), {{param.mojom_name}}Buf.begin(), {{param.mojom_name}}Buf.end()); +{%- endfor %} + +{%- for param in params %} +{%- if param|has_fd %} + {{fds}}.insert({{fds}}.end(), {{param.mojom_name}}Fds.begin(), {{param.mojom_name}}Fds.end()); +{%- endif %} +{%- endfor %} +{%- endmacro -%} + + +{# + # \brief Deserialize a single object from data buffer and fd vector + # + # \param pointer True deserializes the object into a dereferenced pointer + # + # Generate code to deserialize a single object, as specified in \a param, + # from \a buf data buffer and \a fds fd vector. + # This code is meant to be used by macro deserialize_call. + #} +{%- macro deserialize_param(param, pointer, loop, buf, fds) -%} +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( + {{buf}}.begin() + {{param.mojom_name}}Start, +{%- if loop.last %} + {{buf}}.end() +{%- else %} + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize +{%- endif -%} +{{- "," if param|has_fd }} +{%- if param|has_fd %} + {{fds}}.begin() + {{param.mojom_name}}FdStart, +{%- if loop.last %} + {{fds}}.end() +{%- else %} + {{fds}}.begin() + {{param.mojom_name}}FdStart + {{param.mojom_name}}FdsSize +{%- endif -%} +{%- endif -%} +{{- "," if param|needs_control_serializer }} +{%- if param|needs_control_serializer %} + &controlSerializer_ +{%- endif -%} +); +{%- endmacro -%} + + +{# + # \brief Deserialize multiple objects from data buffer and fd vector + # + # \param pointer True deserializes objects into pointers, and adds a null check. + # \param declare True declares the objects in addition to deserialization. + # + # Generate code to deserialize multiple objects, as specified in \a params + # (which are the parameters to some function), from \a buf data buffer and + # \a fds fd vector. + # This code is meant to be used by the proxy, for serializing prior to IPC calls. + #} +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false) -%} +{% set ns = namespace(size_offset = 0) %} +{%- if params|length > 1 %} +{%- for param in params %} + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readUInt({{buf}}, {{ns.size_offset}}); + {%- set ns.size_offset = ns.size_offset + 4 %} +{%- if param|has_fd %} + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readUInt({{buf}}, {{ns.size_offset}}); + {%- set ns.size_offset = ns.size_offset + 4 %} +{%- endif %} +{%- endfor %} +{%- endif %} +{% for param in params %} +{%- if loop.first %} + size_t {{param.mojom_name}}Start = {{ns.size_offset}}; +{%- else %} + size_t {{param.mojom_name}}Start = {{loop.previtem.mojom_name}}Start + {{loop.previtem.mojom_name}}BufSize; +{%- endif %} +{%- endfor %} +{% for param in params|with_fds %} +{%- if loop.first %} + size_t {{param.mojom_name}}FdStart = 0; +{%- elif not loop.last %} + size_t {{param.mojom_name}}FdStart = {{loop.previtem.mojom_name}}FdStart + {{loop.previtem.mojom_name}}FdsSize; +{%- endif %} +{%- endfor %} +{% for param in params %} + {%- if pointer %} + if ({{param.mojom_name}}) { +{{deserialize_param(param, pointer, loop, buf, fds)|indent(16, True)}} + } + {%- else %} + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds)|indent(8)}} + {%- endif %} +{% endfor %} +{%- endmacro -%} diff --git a/utils/ipc/generators/libcamera_templates/serializer.tmpl b/utils/ipc/generators/libcamera_templates/serializer.tmpl new file mode 100644 index 00000000..7f162029 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl @@ -0,0 +1,268 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +{# Turn this into a C macro? #} +{# + # \brief Verify that there is enough bytes to deserialize + # + # Generate code that verifies that \a size is not greater than \a dataSize. + # Otherwise log an error with \a name and \a typename. + #} +{%- macro check_data_size(size, dataSize, name, typename) %} + if ({{size}} > {{dataSize}}) { + LOG(IPADataSerializer, Error) + << "Failed to deserialize {{name}}: not enough {{typename}}, expected " + << ({{size}}) << ", got " << ({{dataSize}}); + return ret; + } +{%- endmacro %} + + +{# + # \brief Serialize some field into return vector + # + # Generate code to serialize \a field into ret_data, including size of the + # field and fds (where appropriate). \a base_controls indicates the + # default ControlInfoMap in the event that the ControlList does not have one. + # This code is meant to be used by the IPADataSerializer specialization. + #} +{%- macro serializer_field(field, base_controls, loop) %} +{%- if field|is_pod or field|is_enum %} + std::vector {{field.mojom_name}}; + std::tie({{field.mojom_name}}, std::ignore) = + {%- if field|is_pod %} + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); + {%- elif field|is_enum %} + IPADataSerializer::serialize(data.{{field.mojom_name}}_); + {%- endif %} + ret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); +{%- elif field|is_fd %} + std::vector {{field.mojom_name}}; + std::vector {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); + ret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + ret_fds.insert(ret_fds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); +{%- elif field|is_controls %} + if (data.{{field.mojom_name}}_.size() > 0) { + std::vector {{field.mojom_name}}; + std::tie({{field.mojom_name}}, std::ignore) = + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, + data.{{field.mojom_name}}_.infoMap() ? *data.{{field.mojom_name}}_.infoMap() : {{base_controls}}, + cs); + appendUInt(ret_data, {{field.mojom_name}}.size()); + ret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + } else { + appendUInt(ret_data, 0); + } +{%- elif field|is_plain_struct or field|is_array or field|is_map %} + std::vector {{field.mojom_name}}; + {%- if field|has_fd %} + std::vector {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + {%- else %} + std::tie({{field.mojom_name}}, std::ignore) = + {%- endif %} + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); + appendUInt(ret_data, {{field.mojom_name}}.size()); + {%- if field|has_fd %} + appendUInt(ret_data, {{field.mojom_name}}Fds.size()); + {%- endif %} + ret_data.insert(ret_data.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + {%- if field|has_fd %} + ret_fds.insert(ret_fds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); + {%- endif %} +{%- else %} + /* Unknown serialization for {{field.mojom_name}}. */ +{%- endif %} +{%- endmacro %} + + +{# + # \brief Deserialize some field into return struct + # + # Generate code to deserialize \a field into object ret. + # This code is meant to be used by the IPADataSerializer specialization. + #} +{%- macro deserializer_field(field, loop) %} +{% if field|is_pod or field|is_enum %} + {%- set field_size = (field|bit_width|int / 8)|int %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }} + {%- if field|is_pod %} + ret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field_size}})); + {%- else %} + ret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(IPADataSerializer::deserialize(m, m + {{field_size}})); + {%- endif %} + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- endif %} +{% elif field|is_fd %} + {%- set field_size = 1 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }} + ret.{{field.mojom_name}}_ = IPADataSerializer<{{field|name}}>::deserialize(m, m + 1, n, n + 1, cs); + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + n += ret.{{field.mojom_name}}_.isValid() ? 1 : 0; + fdsSize -= ret.{{field.mojom_name}}_.isValid() ? 1 : 0; + {%- endif %} +{% elif field|is_controls %} + {%- set field_size = 4 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data') }} + size_t {{field.mojom_name}}Size = readUInt(m); + {%- set field_size = '4 + ' + field.mojom_name + 'Size' -%} + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }} + if ({{field.mojom_name}}Size > 0) + ret.{{field.mojom_name}}_ = + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- endif %} +{% elif field|is_plain_struct or field|is_array or field|is_map%} + {%- set field_size = 4 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data') }} + size_t {{field.mojom_name}}Size = readUInt(m); + {%- if field|has_fd %} + {%- set field_size = 8 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'FdsSize', 'data') }} + size_t {{field.mojom_name}}FdsSize = readUInt(m + 4); + {{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds') }} + {%- endif %} + {%- set field_size = field|has_fd|choose('8 + ', '4 + ') + field.mojom_name + 'Size' -%} + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data') }} + ret.{{field.mojom_name}}_ = + {%- if field|has_fd %} + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); + {%- else %} + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); + {%- endif %} + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- if field|has_fd %} + n += {{field.mojom_name}}FdsSize; + fdsSize -= {{field.mojom_name}}FdsSize; + {%- endif %} + {%- endif %} +{% else %} + /* Unknown deserialization for {{field.mojom_name}}. */ +{%- endif %} +{%- endmacro %} + + +{# + # \brief Serialize a struct + # + # Generate code for IPADataSerializer specialization, for serializing + # \a struct. \a base_controls indicates the default ControlInfoMap + # in the event that the ControlList does not have one. + #} +{%- macro serializer(struct, base_controls) %} + static std::tuple, std::vector> + serialize(const {{ struct.mojom_name }} &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector ret_data; +{%- if struct|has_fd %} + std::vector ret_fds; +{%- endif %} +{%- for field in struct.fields %} +{{serializer_field(field, base_controls, loop)}} +{%- endfor %} +{% if struct|has_fd %} + return {ret_data, ret_fds}; +{%- else %} + return {ret_data, {}}; +{%- endif %} + } +{%- endmacro %} + + +{# + # \brief Deserialize a struct that has fds + # + # Generate code for IPADataSerializer specialization, for deserializing + # \a struct, in the case that \a struct has file descriptors. + #} +{%- macro deserializer_fd(struct) %} + static {{ struct.mojom_name }} + deserialize(std::vector &data, + std::vector &fds, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct.mojom_name}} ret; + std::vector::iterator m = data.begin(); + std::vector::iterator n = fds.begin(); + + size_t dataSize = data.size(); + size_t fdsSize = fds.size(); +{%- for field in struct.fields -%} +{{deserializer_field(field, loop)}} +{%- endfor %} + return ret; + } + + static {{ struct.mojom_name }} + deserialize(std::vector::iterator data_it1, + std::vector::iterator data_it2, + std::vector::iterator fds_it1, + std::vector::iterator fds_it2, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector data(data_it1, data_it2); + std::vector fds(fds_it1, fds_it2); + return IPADataSerializer<{{struct.mojom_name}}>::deserialize(data, fds, cs); + } +{%- endmacro %} + + +{# + # \brief Deserialize a struct that has no fds + # + # Generate code for IPADataSerializer specialization, for deserializing + # \a struct, in the case that \a struct does not have file descriptors. + #} +{%- macro deserializer_no_fd(struct) %} + static {{ struct.mojom_name }} + deserialize(std::vector &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct.mojom_name}} ret; + std::vector::iterator m = data.begin(); + + size_t dataSize = data.size(); +{%- for field in struct.fields -%} +{{deserializer_field(field, loop)}} +{%- endfor %} + return ret; + } + + static {{ struct.mojom_name }} + deserialize(std::vector::iterator data_it1, + std::vector::iterator data_it2, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector data(data_it1, data_it2); + return IPADataSerializer<{{struct.mojom_name}}>::deserialize(data, cs); + } +{%- endmacro %} diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py new file mode 100644 index 00000000..1dd11621 --- /dev/null +++ b/utils/ipc/generators/mojom_libcamera_generator.py @@ -0,0 +1,452 @@ +'''Generates libcamera files from a mojom.Module.''' + +import argparse +import ast +import datetime +import contextlib +import os +import re +import shutil +import sys +import tempfile + +from jinja2 import contextfilter + +import mojom.fileutil as fileutil +import mojom.generate.generator as generator +import mojom.generate.module as mojom +from mojom.generate.template_expander import UseJinja + + +GENERATOR_PREFIX = 'libcamera' + +_kind_to_cpp_type = { + mojom.BOOL: 'bool', + mojom.INT8: 'int8_t', + mojom.UINT8: 'uint8_t', + mojom.INT16: 'int16_t', + mojom.UINT16: 'uint16_t', + mojom.INT32: 'int32_t', + mojom.UINT32: 'uint32_t', + mojom.FLOAT: 'float', + mojom.INT64: 'int64_t', + mojom.UINT64: 'uint64_t', + mojom.DOUBLE: 'double', +} + +_bit_widths = { + mojom.BOOL: '8', + mojom.INT8: '8', + mojom.UINT8: '8', + mojom.INT16: '16', + mojom.UINT16: '16', + mojom.INT32: '32', + mojom.UINT32: '32', + mojom.FLOAT: '32', + mojom.INT64: '64', + mojom.UINT64: '64', + mojom.DOUBLE: '64', +} + +def ModuleName(path): + return path.split('/')[-1].split('.')[0] + +def ModuleClassName(module): + s = re.sub(r'^IPA', '', module.interfaces[0].mojom_name) + return re.sub(r'Interface$', '', s) + +def UpperCamelCase(name): + return ''.join([x.capitalize() for x in generator.SplitCamelCase(name)]) + +def CamelCase(name): + uccc = UpperCamelCase(name) + return uccc[0].lower() + uccc[1:] + +def ConstantStyle(name): + return generator.ToUpperSnakeCase(name) + +def Choose(cond, t, f): + return t if cond else f + +def CommaSep(l): + return ', '.join([m for m in l]) + +def ParamsCommaSep(l): + return ', '.join([m.mojom_name for m in l]) + +def GetDefaultValue(element): + if element.default is not None: + return element.default + if type(element.kind) == mojom.Kind: + return '0' + if mojom.IsEnumKind(element.kind): + return f'static_cast<{element.kind.mojom_name}>(0)' + if (isinstance(element.kind, mojom.Struct) and + element.kind.mojom_name == 'FileDescriptor'): + return '-1' + return '' + +def WithDefaultValues(element): + return [x for x in element if HasDefaultValue(x)] + +def WithFds(element): + return [x for x in element if HasFd(x)] + +def HasDefaultValue(element): + return GetDefaultValue(element) != '' + +def HasDefaultFields(element): + return True in [HasDefaultValue(x) for x in element.fields] + +def GetAllTypes(element): + if mojom.IsArrayKind(element): + return GetAllTypes(element.kind) + if mojom.IsMapKind(element): + return GetAllTypes(element.key_kind) + GetAllTypes(element.value_kind) + if isinstance(element, mojom.Parameter): + return GetAllTypes(element.kind) + if mojom.IsEnumKind(element): + return [element.mojom_name] + if not mojom.IsStructKind(element): + return [element.spec] + if len(element.fields) == 0: + return [element.mojom_name] + ret = [GetAllTypes(x.kind) for x in element.fields] + ret = [x for sublist in ret for x in sublist] + return list(set(ret)) + +def GetAllAttrs(element): + if mojom.IsArrayKind(element): + return GetAllAttrs(element.kind) + if mojom.IsMapKind(element): + return {**GetAllAttrs(element.key_kind), **GetAllAttrs(element.value_kind)} + if isinstance(element, mojom.Parameter): + return GetAllAttrs(element.kind) + if mojom.IsEnumKind(element): + return element.attributes if element.attributes is not None else {} + if mojom.IsStructKind(element) and len(element.fields) == 0: + return element.attributes if element.attributes is not None else {} + if not mojom.IsStructKind(element): + return {} + attrs = [(x.attributes) for x in element.fields] + ret = {} + for d in attrs: + if d is not None: + ret = {**ret, **d} + return ret + +def NeedsControlSerializer(element): + types = GetAllTypes(element) + return "ControlList" in types or "ControlInfoMap" in types + +def HasFd(element): + attrs = GetAllAttrs(element) + if isinstance(element, mojom.Kind): + types = GetAllTypes(element) + else: + types = GetAllTypes(element.kind) + return "FileDescriptor" in types or (attrs is not None and "hasFd" in attrs) + +def MethodInputHasFd(method): + if len([x for x in method.parameters if HasFd(x)]) > 0: + return True + return False + +def MethodOutputHasFd(method): + if (MethodReturnValue(method) != 'void' or + method.response_parameters is None): + return False + if len([x for x in method.parameters if HasFd(x)]) > 0: + return True + return False + +def MethodParamInputs(method): + return method.parameters + +def MethodParamOutputs(method): + if (MethodReturnValue(method) != 'void' or + method.response_parameters is None): + return [] + return method.response_parameters + +def MethodParamNames(method): + params = [] + for param in method.parameters: + params.append(param.mojom_name) + if MethodReturnValue(method) == 'void': + if method.response_parameters is None: + return params + for param in method.response_parameters: + params.append(param.mojom_name) + return params + +def MethodParameters(method): + params = [] + for param in method.parameters: + params.append('const %s %s%s' % (GetNameForElement(param), + ('&' if not IsPod(param) else ''), + param.mojom_name)) + if MethodReturnValue(method) == 'void': + if method.response_parameters is None: + return params + for param in method.response_parameters: + params.append(f'{GetNameForElement(param)} *{param.mojom_name}') + return params + +def MethodReturnValue(method): + if method.response_parameters is None: + return 'void' + if len(method.response_parameters) == 1 and IsPod(method.response_parameters[0]): + return GetNameForElement(method.response_parameters[0]) + return 'void' + +def IsAsync(method): + # callbacks are always async + if re.match("^IPA.*CallbackInterface$", method.interface.mojom_name): + return True + elif re.match("^IPA.*Interface$", method.interface.mojom_name): + if method.attributes is None: + return False + elif 'async' in method.attributes and method.attributes['async']: + return True + return False + +def IsArray(element): + return mojom.IsArrayKind(element.kind) + +def IsControls(element): + return mojom.IsStructKind(element.kind) and (element.kind.mojom_name == "ControlList" or + element.kind.mojom_name == "ControlInfoMap") + +def IsEnum(element): + return mojom.IsEnumKind(element.kind) + +def IsFd(element): + return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "FileDescriptor" + +def IsMap(element): + return mojom.IsMapKind(element.kind) + +def IsPlainStruct(element): + return mojom.IsStructKind(element.kind) and not IsControls(element) and not IsFd(element) + +def IsPod(element): + return element.kind in _kind_to_cpp_type + +def BitWidth(element): + if element.kind in _bit_widths: + return _bit_widths[element.kind] + if mojom.IsEnumKind(element.kind): + return '32' + return '' + +def GetNameForElement(element): + if (mojom.IsEnumKind(element) or + mojom.IsInterfaceKind(element) or + mojom.IsStructKind(element)): + return element.mojom_name + if (mojom.IsArrayKind(element)): + elem_name = GetNameForElement(element.kind) + return f'std::vector<{elem_name}>' + if (mojom.IsMapKind(element)): + key_name = GetNameForElement(element.key_kind) + value_name = GetNameForElement(element.value_kind) + return f'std::map<{key_name}, {value_name}>' + if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)): + if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)): + return GetNameForElement(element.kind) + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): + return _kind_to_cpp_type[element.kind] + return element.kind.mojom_name + if isinstance(element, mojom.EnumValue): + return (GetNameForElement(element.enum) + '.' + + ConstantStyle(element.name)) + if isinstance(element, (mojom.NamedValue, + mojom.Constant, + mojom.EnumField)): + return ConstantStyle(element.name) + if (hasattr(element, '__hash__') and element in _kind_to_cpp_type): + return _kind_to_cpp_type[element] + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): + return _kind_to_cpp_type[element.kind] + if (hasattr(element, 'spec')): + return element.spec.split(':')[-1] + if (mojom.IsInterfaceRequestKind(element) or + mojom.IsAssociatedKind(element) or + mojom.IsPendingRemoteKind(element) or + mojom.IsPendingReceiverKind(element) or + mojom.IsUnionKind(element)): + raise Exception('Unsupported element: %s' % element) + raise Exception('Unexpected element: %s' % element) + +def ValidateZeroLength(l, s, cap=True): + if len(l) > 0: + raise Exception(f'{s.capitalize() if cap else s} should be empty') + +def ValidateSingleLength(l, s, cap=True): + if len(l) > 1: + raise Exception(f'Only one {s} allowed') + if len(l) < 1: + raise Exception(f'{s.capitalize() if cap else s} is required') + +def ValidateInterfaces(interfaces): + # Validate presence of main interface + intf = [x for x in interfaces + if re.match("^IPA.*Interface", x.mojom_name) and + not re.match("^IPA.*CallbackInterface", x.mojom_name)] + ValidateSingleLength(intf, 'main interface') + + # Validate presence of callback interface + cb = [x for x in interfaces if re.match("^IPA.*CallbackInterface", x.mojom_name)] + ValidateSingleLength(cb, 'callback interface') + + # Validate required main interface functions + intf = intf[0] + f_init = [x for x in intf.methods if x.mojom_name == 'init'] + f_start = [x for x in intf.methods if x.mojom_name == 'start'] + f_stop = [x for x in intf.methods if x.mojom_name == 'stop'] + + ValidateSingleLength(f_init, 'init()', False) + ValidateSingleLength(f_start, 'start()', False) + ValidateSingleLength(f_stop, 'stop()', False) + + f_init = f_init[0] + f_start = f_start[0] + f_stop = f_stop[0] + + # Validate parameters to init() + ValidateSingleLength(f_init.parameters, 'input parameter to init()') + ValidateSingleLength(f_init.response_parameters, 'output parameter from init()') + if f_init.parameters[0].kind.mojom_name != 'IPASettings': + raise Exception('init() must have single IPASettings input parameter') + if f_init.response_parameters[0].kind.spec != 'i32': + raise Exception('init() must have single int32 output parameter') + + # Validate parameters to start() + ValidateZeroLength(f_start.parameters, 'input parameter to start()') + ValidateSingleLength(f_start.response_parameters, 'output parameter from start()') + if f_start.response_parameters[0].kind.spec != 'i32': + raise Exception('start() must have single int32 output parameter') + + # Validate parameters to stop() + ValidateZeroLength(f_stop.parameters, 'input parameter to stop()') + ValidateZeroLength(f_stop.parameters, 'output parameter from stop()') + +class Generator(generator.Generator): + + @staticmethod + def GetTemplatePrefix(): + return 'libcamera_templates' + + def GetFilters(self): + libcamera_filters = { + 'all_types': GetAllTypes, + 'bit_width': BitWidth, + 'choose': Choose, + 'comma_sep': CommaSep, + 'default_value': GetDefaultValue, + 'has_default_fields': HasDefaultFields, + 'has_fd': HasFd, + 'is_async': IsAsync, + 'is_array': IsArray, + 'is_controls': IsControls, + 'is_enum': IsEnum, + 'is_fd': IsFd, + 'is_map': IsMap, + 'is_plain_struct': IsPlainStruct, + 'is_pod': IsPod, + 'method_input_has_fd': MethodInputHasFd, + 'method_output_has_fd': MethodOutputHasFd, + 'method_param_names': MethodParamNames, + 'method_param_inputs': MethodParamInputs, + 'method_param_outputs': MethodParamOutputs, + 'method_parameters': MethodParameters, + 'method_return_value': MethodReturnValue, + 'name': GetNameForElement, + 'needs_control_serializer': NeedsControlSerializer, + 'params_comma_sep': ParamsCommaSep, + 'with_default_values': WithDefaultValues, + 'with_fds': WithFds, + } + return libcamera_filters + + def _GetJinjaExports(self): + return { + 'base_controls': '%sControls' % ModuleClassName(self.module), + 'cmd_enum_name': '%sCMD' % ModuleClassName(self.module), + 'enums': self.module.enums, + 'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0, + 'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0, + 'has_namespace': self.module.namespace is not None, + 'imports': self.module.imports, + 'interface_cb': self.module.interfaces[1], + 'interface_main': self.module.interfaces[0], + 'interface_name': 'IPA%sInterface' % ModuleClassName(self.module), + 'ipc_name': 'IPAIPCUnixSocket', + 'kinds': self.module.kinds, + 'module': self.module, + 'module_name': ModuleName(self.module.path), + 'module_class_name': ModuleClassName(self.module), + 'namespace': self.module.namespace, + 'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module), + 'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module), + 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0], + 'year': datetime.datetime.now().year, + } + + + @UseJinja('module_generated.h.tmpl') + def _GenerateDataHeader(self): + return self._GetJinjaExports() + + @UseJinja('module_serializer.h.tmpl') + def _GenerateSerializer(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy.cpp.tmpl') + def _GenerateProxyCpp(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy.h.tmpl') + def _GenerateProxyHeader(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy_worker.cpp.tmpl') + def _GenerateProxyWorker(self): + return self._GetJinjaExports() + + def GenerateFiles(self, unparsed_args): + parser = argparse.ArgumentParser() + parser.add_argument('--libcamera_generate_header', action='store_true') + parser.add_argument('--libcamera_generate_serializer', action='store_true') + parser.add_argument('--libcamera_generate_proxy_cpp', action='store_true') + parser.add_argument('--libcamera_generate_proxy_h', action='store_true') + parser.add_argument('--libcamera_generate_proxy_worker', action='store_true') + parser.add_argument('--libcamera_output_path') + args = parser.parse_args(unparsed_args) + + ValidateInterfaces(self.module.interfaces) + + fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path)) + + module_name = ModuleName(self.module.path) + + if args.libcamera_generate_header: + self.Write(self._GenerateDataHeader(), + args.libcamera_output_path) + + if args.libcamera_generate_serializer: + self.Write(self._GenerateSerializer(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_cpp: + self.Write(self._GenerateProxyCpp(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_h: + self.Write(self._GenerateProxyHeader(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_worker: + self.Write(self._GenerateProxyWorker(), + args.libcamera_output_path)