Message ID | 20201106103707.49660-5-paul.elder@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Paul, On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > 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 <paul.elder@ideasonboard.com> > > --- > Changes in v4: > For the non-template files: > - rename IPA{pipeline_name}CallbackInterface to > IPA{pipeline_name}EventInterface > - to avoid the notion of "callback" and emphasize that it's an event > - add support for strings in custom structs > - add validation, that async methods must not have return values > - it throws exception and isn't very clear though...? > - rename controls to libcamera::{pipeline_name}::controls (controls is > now lowercase) > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > - same for their corresponding template files > For the template files: > - fix spacing (now it's all {{var}} instead of some {{ var }}) > - except if it's code, so code is still {{ code }} > - move inclusion of corresponding header to first in the inclusion list > - fix copy&paste errors > - change snake_case to camelCase in the generated code > - template code still uses snake_case > - change the generated command enums to an enum class, and make it > capitalized (instead of allcaps) > - add length checks to recvIPC (in proxy) > - fix some template spacing > - don't use const for PODs in function/signal parameters > - add the proper length checks to readPOD/appendPOD > - the helper functions for reading and writing PODs to and from > serialized data > - rename readUInt/appendUInt to readPOD/appendPOD > - add support for strings in custom structs > > Changes in v3: > - add support for namespaces > - fix enum assignment (used to have +1 for CMD applied to all enums) > - use readHeader, writeHeader, and eraseHeader as static class functions > of IPAIPCUnixSocket (in the proxy worker) > - add requirement that base controls *must* be defined in > libcamera::{pipeline_name}::Controls > > 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_ipa_interface.h.tmpl | 113 ++++ > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > .../module_ipa_proxy.h.tmpl | 118 +++++ > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > .../module_ipa_serializer.h.tmpl | 44 ++ > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > 8 files changed, 1673 insertions(+) > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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_ipa_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_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > new file mode 100644 > index 00000000..a470b99e > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > @@ -0,0 +1,113 @@ > +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) {{year}}, Google Inc. Wondering if the copyright shouldn't be attribute to who writes the template. Apart from this, thanks for the monstrous patch, that's a template, I'm not reviewing it in detail > + * > + * {{module_name}}_ipa_interface.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 <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}.h> > + > +{% if has_map %}#include <map>{% endif %} > +{% if has_array %}#include <vector>{% endif %} > + > +namespace libcamera { > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +enum class {{cmd_enum_name}} { > + Exit = 0, > +{%- for method in interface_main.methods %} > + {{method.mojom_name|cap}} = {{loop.index}}, > +{%- endfor %} > +}; > + > +enum class {{cmd_event_enum_name}} { > +{%- for method in interface_cb.methods %} > + {{method.mojom_name|cap}} = {{loop.index}}, > +{%- endfor %} > +}; > + > +{% for enum in enums %} > +enum {{enum.mojom_name}} { > +{%- for field in enum.fields %} > + {{field.mojom_name}} = {{field.numeric_value}}, > +{%- 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 %} > + > +{#- > +Any consts or #defines should be moved to the mojom file when possible. > +If anything needs to be #included, then {{module_name}}.h needs to have the > +#include. > +#} > +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > + {{- ", " if not loop.last}} > +{%- endfor -%} > +> {{method.mojom_name}}; > +{% endfor -%} > +}; > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ > +{% endfor %} > +{%- endif %} > +} /* 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..9328c7ca > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl > @@ -0,0 +1,238 @@ > +{#- 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 <libcamera/ipa/ipa_proxy_{{module_name}}.h> > + > +#include <vector> > + > +#include <libcamera/ipa/ipa_module_info.h> > +#include <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > + > +#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" > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(IPAProxy) > + > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) > + : IPAProxy(ipam), running_(false), > + isolate_(isolate) > +{ > + LOG(IPAProxy, Debug) > + << "initializing {{module_name}} proxy: loading IPA from " > + << ipam->path(); > + > + if (isolate_) { > + const std::string proxyWorkerPath = resolvePath("ipa_proxy_{{module_name}}"); > + if (proxyWorkerPath.empty()) { > + LOG(IPAProxy, Error) > + << "Failed to get proxy worker path"; > + return; > + } > + > + ipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxyWorkerPath.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(static_cast<uint32_t>({{cmd_enum_name}}::Exit), {}, {}); > +} > + > +{% if interface_cb.methods|length > 0 %} > +void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds) > +{ > + if (data.size() < 8) { > + LOG(IPAProxy, Error) > + << "Didn't receive enough bytes to parse event"; > + return; > + } > + > + {{cmd_event_enum_name}} cmd = static_cast<{{cmd_event_enum_name}}>(( > + data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); > + > + /* Need to skip another 4 bytes for the sequence number. */ > + std::vector<uint8_t>::iterator vec = data.begin() + 8; > + size_t dataSize = data.size() - 8; > + switch (cmd) { > +{%- for method in interface_cb.methods %} > + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { > + {{method.mojom_name}}IPC(vec, dataSize, fds); > + break; > + } > +{%- endfor %} > + default: > + LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(cmd); > + } > +} > +{%- 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<uint8_t> _ipcInputBuf; > +{%- if method|method_input_has_fd %} > + std::vector<int32_t> _ipcInputFds; > +{%- endif %} > +{%- endif %} > +{%- if has_output %} > + std::vector<uint8_t> _ipcOutputBuf; > +{%- if method|method_output_has_fd %} > + std::vector<int32_t> _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_enum_name + "::" + method.mojom_name|cap %} > +{% if method|is_async %} > + int ret = ipc_->sendAsync(static_cast<uint32_t>({{cmd}}), {{input_buf}}, {{fds_buf}}); > +{%- else %} > + int ret = ipc_->sendSync(static_cast<uint32_t>({{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 %} > + } > +{% 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<uint8_t>::iterator data, > + size_t dataSize, > + [[maybe_unused]] std::vector<int32_t> &fds) > +{ > +{%- for param in method.parameters %} > + {{param|name}} {{param.mojom_name}}; > +{%- endfor %} > +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} > + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); > +} > +{% endfor %} > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ > +{% endfor %} > +{%- endif %} > +} /* 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..3fb7192f > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl > @@ -0,0 +1,118 @@ > +{#- 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 <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > + > +#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 { > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > + {{- ", " if not loop.last}} > +{%- endfor -%} > +> {{method.mojom_name}}; > +{% endfor %} > + > +private: > + void recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &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<uint8_t>::iterator data, > + size_t dataSize, > + std::vector<int32_t> &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<IPAIPCUnixSocket> ipc_; > + > + ControlSerializer controlSerializer_; > +}; > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ > +{% endfor %} > +{%- endif %} > +} /* 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..dca4f99d > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl > @@ -0,0 +1,187 @@ > +{#- 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 <algorithm> > +#include <iostream> > +#include <sys/types.h> > +#include <tuple> > +#include <unistd.h> > + > +#include <libcamera/event_dispatcher.h> > +#include <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > +#include <libcamera/logging.h> > + > +#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) > + > +{%- if has_namespace %} > +{% for ns in namespace -%} > +using namespace {{ns}}; > +{% endfor %} > +{%- endif %} > + > +struct CallData { > + IPCUnixSocket::Payload *response; > + bool done; > +}; > + > +{{interface_name}} *ipa_; > +IPCUnixSocket socket_; > + > +ControlSerializer controlSerializer_; > + > +bool exit_ = false; > + > +void readyRead(IPCUnixSocket *socket) > +{ > + IPCUnixSocket::Payload _message, _response; > + int _retRecv = socket->receive(&_message); > + if (_retRecv) { > + LOG({{proxy_name}}Worker, Error) > + << "Receive message failed" << _retRecv; > + return; > + } > + > + uint32_t _cmdUint, _seq; > + std::tie(_cmdUint, _seq) = IPAIPCUnixSocket::readHeader(_message); > + IPAIPCUnixSocket::eraseHeader(_message); > + > + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_cmdUint); > + > + switch (_cmd) { > + case {{cmd_enum_name}}::Exit: { > + exit_ = true; > + break; > + } > + > +{% for method in interface_main.methods %} > + case {{cmd_enum_name}}::{{method.mojom_name|cap}}: { > + {{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 %} > + IPAIPCUnixSocket::writeHeader(_response, _cmdUint, _seq); > +{%- if method|method_return_value != "void" %} > + std::vector<uint8_t> _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 %} > + default: > + LOG({{proxy_name}}Worker, Error) << "Unknown command " << _cmdUint; > + } > +} > + > +{% for method in interface_cb.methods %} > +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} > +{ > + IPCUnixSocket::Payload _message; > + > + IPAIPCUnixSocket::writeHeader(_message, static_cast<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), 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, Error) > + << "Tried to start worker with no args"; > + return EXIT_FAILURE; > + } > + > + int fd = std::stoi(argv[2]); > + LOG({{proxy_name}}Worker, Info) > + << "Starting worker for IPA module " << argv[1] > + << " with IPC fd = " << fd; > + > + std::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]); > + if (!ipam->isValid() || !ipam->load()) { > + LOG({{proxy_name}}Worker, Error) > + << "IPAModule " << argv[1] << " isn't valid"; > + 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_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl > new file mode 100644 > index 00000000..675f9adf > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_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 <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > + > +#include "libcamera/internal/control_serializer.h" > +#include "libcamera/internal/ipa_data_serializer.h" > + > +#include <tuple> > +#include <vector> > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(IPADataSerializer) > +{% for struct in structs_nonempty %} > +template<> > +class IPADataSerializer<{{struct|name_full(namespace_str)}}> > +{ > +public: > +{{- serializer.serializer(struct, base_controls, namespace_str)}} > +{%- if struct|has_fd %} > +{{serializer.deserializer_fd(struct, namespace_str)}} > +{%- else %} > +{{serializer.deserializer_no_fd(struct, namespace_str)}} > +{%- 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..f6836034 > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl > @@ -0,0 +1,205 @@ > +{#- 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<uint8_t> {{param.mojom_name}}Buf; > +{%- if param|has_fd %} > + std::vector<int32_t> {{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 %} > + appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size()); > +{%- if param|has_fd %} > + appendPOD<uint32_t>({{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 > + # \param iter True treats \a buf as an iterator instead of a vector > + # \param data_size Variable that holds the size of the vector referenced by \a buf > + # > + # 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, iter, data_size) -%} > +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( > +{%- if not iter %} > + {{buf}}.begin() + {{param.mojom_name}}Start, > +{%- else %} > + {{buf}} + {{param.mojom_name}}Start, > +{%- endif %} > +{%- if loop.last and not iter %} > + {{buf}}.end() > +{%- elif not iter %} > + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > +{%- elif iter and loop.length == 1 %} > + {{buf}} + {{data_size}} > +{%- else %} > + {{buf}} + {{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. > + # \param iter True treats \a buf as an iterator instead of a vector > + # \param data_size Variable that holds the size of the vector referenced by \a buf > + # > + # 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 deserializing after IPC calls. > + #} > +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%} > +{% set ns = namespace(size_offset = 0) %} > +{%- if params|length > 1 %} > +{%- for param in params %} > + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > +{%- if iter -%} > +, {{buf}} + {{data_size}} > +{%- endif -%} > +); > + {%- set ns.size_offset = ns.size_offset + 4 %} > +{%- if param|has_fd %} > + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > +{%- if iter -%} > +, {{buf}} + {{data_size}} > +{%- endif -%} > +); > + {%- 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, iter, data_size)|indent(16, True)}} > + } > + {%- else %} > + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds, iter, data_size)|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..51dbeb0e > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl > @@ -0,0 +1,280 @@ > +{#- 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 retData, 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, namespace, loop) %} > +{%- if field|is_pod or field|is_enum %} > + std::vector<uint8_t> {{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<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}}_); > + {%- endif %} > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > +{%- elif field|is_fd %} > + std::vector<uint8_t> {{field.mojom_name}}; > + std::vector<int32_t> {{field.mojom_name}}Fds; > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > +{%- elif field|is_controls %} > + if (data.{{field.mojom_name}}_.size() > 0) { > + std::vector<uint8_t> {{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); > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + } else { > + appendPOD<uint32_t>(retData, 0); > + } > +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} > + std::vector<uint8_t> {{field.mojom_name}}; > + {%- if field|has_fd %} > + std::vector<int32_t> {{field.mojom_name}}Fds; > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > + {%- else %} > + std::tie({{field.mojom_name}}, std::ignore) = > + {%- endif %} > + {%- if field|is_array or field|is_map %} > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); > + {%- elif field|is_str %} > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > + {%- else %} > + IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}_, cs); > + {%- endif %} > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > + {%- if field|has_fd %} > + appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size()); > + {%- endif %} > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + {%- if field|has_fd %} > + retFds.insert(retFds.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, namespace, 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<uint{{field|bit_width}}_t>::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 = readPOD<uint32_t>(m, 0, data.end()); > + {%- 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 or field|is_str %} > + {%- set field_size = 4 %} > + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} > + size_t {{field.mojom_name}}Size = readPOD<uint32_t>(m, 0, data.end()); > + {%- 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 = readPOD<uint32_t>(m, 4, data.end()); > + {{- 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|is_str %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size); > + {%- elif field|has_fd and (field|is_array or field|is_map) %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > + {%- elif field|has_fd and (not (field|is_array or field|is_map)) %} > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > + {%- elif (not field|has_fd) and (field|is_array or field|is_map) %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > + {%- else %} > + IPADataSerializer<{{field|name_full(namespace)}}>::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, namespace) %} > + static std::tuple<std::vector<uint8_t>, std::vector<int32_t>> > + serialize(const {{struct|name_full(namespace)}} &data, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> retData; > +{%- if struct|has_fd %} > + std::vector<int32_t> retFds; > +{%- endif %} > +{%- for field in struct.fields %} > +{{serializer_field(field, base_controls, namespace, loop)}} > +{%- endfor %} > +{% if struct|has_fd %} > + return {retData, retFds}; > +{%- else %} > + return {retData, {}}; > +{%- 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, namespace) %} > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t> &data, > + std::vector<int32_t> &fds, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + {{struct|name_full(namespace)}} ret; > + std::vector<uint8_t>::iterator m = data.begin(); > + std::vector<int32_t>::iterator n = fds.begin(); > + > + size_t dataSize = data.size(); > + size_t fdsSize = fds.size(); > +{%- for field in struct.fields -%} > +{{deserializer_field(field, namespace, loop)}} > +{%- endfor %} > + return ret; > + } > + > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t>::iterator dataBegin, > + std::vector<uint8_t>::iterator dataEnd, > + std::vector<int32_t>::iterator fdsBegin, > + std::vector<int32_t>::iterator fdsEnd, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> data(dataBegin, dataEnd); > + std::vector<int32_t> fds(fdsBegin, fdsEnd); > + return IPADataSerializer<{{struct|name_full(namespace)}}>::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, namespace) %} > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t> &data, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + {{struct|name_full(namespace)}} ret; > + std::vector<uint8_t>::iterator m = data.begin(); > + > + size_t dataSize = data.size(); > +{%- for field in struct.fields -%} > +{{deserializer_field(field, namespace, loop)}} > +{%- endfor %} > + return ret; > + } > + > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t>::iterator dataBegin, > + std::vector<uint8_t>::iterator dataEnd, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> data(dataBegin, dataEnd); > + return IPADataSerializer<{{struct|name_full(namespace)}}>::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..c4fbf9b3 > --- /dev/null > +++ b/utils/ipc/generators/mojom_libcamera_generator.py > @@ -0,0 +1,488 @@ > +'''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 Capitalize(name): > + return name[0].upper() + name[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.*EventInterface$", 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 IsStr(element): > + return element.kind.spec == 's' > + > +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 (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): > + return 'std::string' > + 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')): > + if (element.spec == 's'): > + return 'std::string' > + 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 GetFullNameForElement(element, namespace_str): > + name = GetNameForElement(element) > + if namespace_str == '': > + return name > + return f'{namespace_str}::{name}' > + > +def ValidateZeroLength(l, s, cap=True): > + if l is None: > + return > + 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.*EventInterface", x.mojom_name)] > + ValidateSingleLength(intf, 'main interface') > + intf = intf[0] > + > + # Validate presence of callback interface > + cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] > + ValidateSingleLength(cb, 'event interface') > + cb = cb[0] > + > + # Validate required main interface functions > + 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()') > + > + # Validate that all async methods don't have return values > + intf_methods_async = [x for x in intf.methods if IsAsync(x)] > + for method in intf_methods_async: > + ValidateZeroLength(method.response_parameters, > + f'{method.mojom_name} response parameters', False) > + > + cb_methods_async = [x for x in cb.methods if IsAsync(x)] > + for method in cb_methods_async: > + ValidateZeroLength(method.response_parameters, > + f'{method.mojom_name} response parameters', False) > + > +class Generator(generator.Generator): > + > + @staticmethod > + def GetTemplatePrefix(): > + return 'libcamera_templates' > + > + def GetFilters(self): > + libcamera_filters = { > + 'all_types': GetAllTypes, > + 'bit_width': BitWidth, > + 'cap': Capitalize, > + '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, > + 'is_str': IsStr, > + '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, > + 'name_full': GetFullNameForElement, > + 'needs_control_serializer': NeedsControlSerializer, > + 'params_comma_sep': ParamsCommaSep, > + 'with_default_values': WithDefaultValues, > + 'with_fds': WithFds, > + } > + return libcamera_filters > + > + def _GetJinjaExports(self): > + return { > + 'base_controls': '%s::controls' % ModuleClassName(self.module), > + 'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module), > + 'cmd_event_enum_name': '_%sEventCmd' % 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.mojom_namespace != '', > + '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.mojom_namespace.split('.'), > + 'namespace_str': self.module.mojom_namespace.replace('.', '::') if > + self.module.mojom_namespace is not None else '', > + '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_ipa_interface.h.tmpl') > + def _GenerateDataHeader(self): > + return self._GetJinjaExports() > + > + @UseJinja('module_ipa_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) A very cursory look: Acked-by: Jacopo Mondi <jacopo@jmondi.org> Thanks j > -- > 2.27.0 > > _______________________________________________ > libcamera-devel mailing list > libcamera-devel@lists.libcamera.org > https://lists.libcamera.org/listinfo/libcamera-devel
Hi Paul, Thank you for the patch. On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > 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 <paul.elder@ideasonboard.com> > > --- > Changes in v4: > For the non-template files: > - rename IPA{pipeline_name}CallbackInterface to > IPA{pipeline_name}EventInterface > - to avoid the notion of "callback" and emphasize that it's an event > - add support for strings in custom structs > - add validation, that async methods must not have return values > - it throws exception and isn't very clear though...? > - rename controls to libcamera::{pipeline_name}::controls (controls is > now lowercase) > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > - same for their corresponding template files > For the template files: > - fix spacing (now it's all {{var}} instead of some {{ var }}) > - except if it's code, so code is still {{ code }} > - move inclusion of corresponding header to first in the inclusion list > - fix copy&paste errors > - change snake_case to camelCase in the generated code > - template code still uses snake_case > - change the generated command enums to an enum class, and make it > capitalized (instead of allcaps) > - add length checks to recvIPC (in proxy) > - fix some template spacing > - don't use const for PODs in function/signal parameters > - add the proper length checks to readPOD/appendPOD > - the helper functions for reading and writing PODs to and from > serialized data > - rename readUInt/appendUInt to readPOD/appendPOD > - add support for strings in custom structs > > Changes in v3: > - add support for namespaces > - fix enum assignment (used to have +1 for CMD applied to all enums) > - use readHeader, writeHeader, and eraseHeader as static class functions > of IPAIPCUnixSocket (in the proxy worker) > - add requirement that base controls *must* be defined in > libcamera::{pipeline_name}::Controls > > 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_ipa_interface.h.tmpl | 113 ++++ > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > .../module_ipa_proxy.h.tmpl | 118 +++++ > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > .../module_ipa_serializer.h.tmpl | 44 ++ > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > 8 files changed, 1673 insertions(+) > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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_ipa_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 Let's start with the generator. [snip] > diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py > new file mode 100644 > index 00000000..c4fbf9b3 > --- /dev/null > +++ b/utils/ipc/generators/mojom_libcamera_generator.py > @@ -0,0 +1,488 @@ Missing SPDX header and copyright notice. > +'''Generates libcamera files from a mojom.Module.''' Comments in Python use # :-) > + > +import argparse > +import ast > +import datetime > +import contextlib > +import os > +import re > +import shutil > +import sys > +import tempfile ast, contextlib, shutil, sys and tempfile don't seem to be used. > + > +from jinja2 import contextfilter contextfilter isn't used either. > + > +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) You could combine the two with return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1), module) and possibly optimize it further by pre-compiling the regular expression. I'll let you decide if it's worth it. > + > +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:] These two functions don't seem to be used. > + > +def Capitalize(name): > + return name[0].upper() + name[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'): Is there a need for the outer parentheses ? Or is this dictated by the mojom coding style ? Same comment for several locations below. > + 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)] Should these be moved a little bit further down, after the functions they call ? The code works fine, but it would create an easier to read flow. > + > +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} Maybe ret.update(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: You can drop the len() call, [] evaluates to False. > + 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: Same here. Shouldn't it be method.response_parameters instead of method.parameters ? > + 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 The increase code reuse, you could implement these functions as follows. 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 MethodParamHasFd(parameters): if [x for x in parameters if HasFd(x)]: return True return False def MethodInputHasFd(method): return MethodParamHasFd(MethodParamInputs(method)) def MethodOutputHasFd(method): return MethodParamHasFd(MethodParamOutputs(method)) > + > +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 ''), No need for the outer parentheses. > + 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 s/callbacks/Events/ ? > + if re.match("^IPA.*EventInterface$", 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 IsStr(element): > + return element.kind.spec == 's' > + > +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 (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): > + return 'std::string' > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): Is the hasattr() check needed ? If element has no kind attribute, I would expect the previous two if's to raise an exception. > + return _kind_to_cpp_type[element.kind] > + return element.kind.mojom_name > + if isinstance(element, mojom.EnumValue): Extra space after comma. > + 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] For my own education, what type of elements does this cover ? > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): > + return _kind_to_cpp_type[element.kind] > + if (hasattr(element, 'spec')): > + if (element.spec == 's'): > + return 'std::string' > + 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 GetFullNameForElement(element, namespace_str): > + name = GetNameForElement(element) > + if namespace_str == '': > + return name > + return f'{namespace_str}::{name}' > + > +def ValidateZeroLength(l, s, cap=True): > + if l is None: > + return > + 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.*EventInterface", x.mojom_name)] > + ValidateSingleLength(intf, 'main interface') > + intf = intf[0] > + > + # Validate presence of callback interface s/callback/event/ > + cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] s/cb/event/ ? > + ValidateSingleLength(cb, 'event interface') > + cb = cb[0] > + > + # Validate required main interface functions > + 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()') > + > + # Validate that all async methods don't have return values > + intf_methods_async = [x for x in intf.methods if IsAsync(x)] > + for method in intf_methods_async: > + ValidateZeroLength(method.response_parameters, > + f'{method.mojom_name} response parameters', False) > + > + cb_methods_async = [x for x in cb.methods if IsAsync(x)] s/cb_method_async/event_methods_async/ > + for method in cb_methods_async: > + ValidateZeroLength(method.response_parameters, > + f'{method.mojom_name} response parameters', False) > + > +class Generator(generator.Generator): > + > + @staticmethod > + def GetTemplatePrefix(): > + return 'libcamera_templates' > + > + def GetFilters(self): > + libcamera_filters = { > + 'all_types': GetAllTypes, > + 'bit_width': BitWidth, > + 'cap': Capitalize, 'cap': str.capitalize, and drop the custom Capitalize function. > + '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, > + 'is_str': IsStr, > + '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, > + 'name_full': GetFullNameForElement, > + 'needs_control_serializer': NeedsControlSerializer, > + 'params_comma_sep': ParamsCommaSep, > + 'with_default_values': WithDefaultValues, > + 'with_fds': WithFds, > + } > + return libcamera_filters > + > + def _GetJinjaExports(self): Would it make sense to store the return value of ModuleClassName(self.module) in a local variable instead of calling the function several times below ? If self.module is constant during the lifetime of the Generator (I think it is), you could even compute it in the constructor and store it in a member variable. > + return { > + 'base_controls': '%s::controls' % ModuleClassName(self.module), > + 'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module), > + 'cmd_event_enum_name': '_%sEventCmd' % 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.mojom_namespace != '', > + 'imports': self.module.imports, I don't see any mention of 'imports' in the templates. Is this used internally by mojom and/or jinja, or is it unused ? > + 'interface_cb': self.module.interfaces[1], interface_event ? > + 'interface_main': self.module.interfaces[0], Is there a guarantee the interfaces will always be specified in this order ? The code in ValidateInterfaces() finds interfaces based on their name. > + 'interface_name': 'IPA%sInterface' % ModuleClassName(self.module), > + 'ipc_name': 'IPAIPCUnixSocket', This seems unused. > + 'kinds': self.module.kinds, > + 'module': self.module, Same for those two (unless they are used elsewhere than in the templates). > + 'module_name': ModuleName(self.module.path), > + 'module_class_name': ModuleClassName(self.module), Ditto. > + 'namespace': self.module.mojom_namespace.split('.'), > + 'namespace_str': self.module.mojom_namespace.replace('.', '::') if > + self.module.mojom_namespace is not None else '', > + 'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module), > + 'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module), Same here. > + 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0], > + 'year': datetime.datetime.now().year, > + } > + > + > + @UseJinja('module_ipa_interface.h.tmpl') > + def _GenerateDataHeader(self): > + return self._GetJinjaExports() > + > + @UseJinja('module_ipa_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) This seems unused. > + > + 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) I'm sure we could avoid the manual unrolling of all this, using a dictionary to store the template files and iterating over it to add arguments to the parser and replacing the above code, but it's probably not worth it as I don't expect this will change a lot.
Hi Paul, Thank you for the patch. On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > 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 <paul.elder@ideasonboard.com> > > --- > Changes in v4: > For the non-template files: > - rename IPA{pipeline_name}CallbackInterface to > IPA{pipeline_name}EventInterface > - to avoid the notion of "callback" and emphasize that it's an event > - add support for strings in custom structs > - add validation, that async methods must not have return values > - it throws exception and isn't very clear though...? > - rename controls to libcamera::{pipeline_name}::controls (controls is > now lowercase) > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > - same for their corresponding template files > For the template files: > - fix spacing (now it's all {{var}} instead of some {{ var }}) > - except if it's code, so code is still {{ code }} > - move inclusion of corresponding header to first in the inclusion list > - fix copy&paste errors > - change snake_case to camelCase in the generated code > - template code still uses snake_case > - change the generated command enums to an enum class, and make it > capitalized (instead of allcaps) > - add length checks to recvIPC (in proxy) > - fix some template spacing > - don't use const for PODs in function/signal parameters > - add the proper length checks to readPOD/appendPOD > - the helper functions for reading and writing PODs to and from > serialized data > - rename readUInt/appendUInt to readPOD/appendPOD > - add support for strings in custom structs > > Changes in v3: > - add support for namespaces > - fix enum assignment (used to have +1 for CMD applied to all enums) > - use readHeader, writeHeader, and eraseHeader as static class functions > of IPAIPCUnixSocket (in the proxy worker) > - add requirement that base controls *must* be defined in > libcamera::{pipeline_name}::Controls > > 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_ipa_interface.h.tmpl | 113 ++++ > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > .../module_ipa_proxy.h.tmpl | 118 +++++ > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > .../module_ipa_serializer.h.tmpl | 44 ++ > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > 8 files changed, 1673 insertions(+) > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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 The proxy .h and .cpp files are named ipa_proxy_{pipeline}.{h,cpp}, while the other generated files are named {pipeline}_ipa_interface.h and {pipeline}_ipa_serializer.h. Could you pick one naming convention, and apply it to all files ? The .tmpl files should then follow the same convention. > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_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 And now, for the templates. > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > new file mode 100644 > index 00000000..a470b99e > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > @@ -0,0 +1,113 @@ > +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) {{year}}, Google Inc. Coming back to the question of copyright on generated code. First of all, the template itself is a creative work, which should have a copyright notice, with a fixed date. This should go to the comment at the beginning of the file. The generated file is the result of a non-creative process that takes creative work as input. As such, it is covered by the copyright of the template, and the mojom file. Would it be difficult to extract the copyright data of the .mojom file and add it here ? I'm thnking about the following at the beginning of the template: {#- # SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, Google Inc. -#} /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * {{copyright}} * * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} * * This file is auto-generated. Do not edit. */ with {{copyright}} being replaced with the copyright statement from the .mojom file. Mojo won't give you the information, but if you pass the template name to _GetJinjaExport(), it may not be too difficult to extract the copyright line. This may not be worth it, so feel free to just have a fixed copyright here: /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} * * This file is auto-generated. Do not edit. */ > + * > + * {{module_name}}_ipa_interface.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 <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}.h> > + > +{% if has_map %}#include <map>{% endif %} > +{% if has_array %}#include <vector>{% endif %} > + > +namespace libcamera { > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +enum class {{cmd_enum_name}} { > + Exit = 0, > +{%- for method in interface_main.methods %} > + {{method.mojom_name|cap}} = {{loop.index}}, > +{%- endfor %} > +}; > + > +enum class {{cmd_event_enum_name}} { > +{%- for method in interface_cb.methods %} > + {{method.mojom_name|cap}} = {{loop.index}}, > +{%- endfor %} > +}; > + > +{% for enum in enums %} > +enum {{enum.mojom_name}} { > +{%- for field in enum.fields %} > + {{field.mojom_name}} = {{field.numeric_value}}, > +{%- 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}}() {} I think you can skip empty destructors. > + > + {{struct.mojom_name}}( > +{%- for field in struct.fields -%} > +{{field|name}} {{field.mojom_name}}{{", " if not loop.last}} Should non-trivial parameters be passed by const reference instead of value ? > +{%- 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 %} > + > +{#- > +Any consts or #defines should be moved to the mojom file when possible. > +If anything needs to be #included, then {{module_name}}.h needs to have the > +#include. > +#} > +class {{interface_name}} : public IPAInterface > +{ > +public: > + virtual ~{{interface_name}}() {} The base class has a virtual destructor already, so you can drop this one. > +{% 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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > + {{- ", " if not loop.last}} > +{%- endfor -%} > +> {{method.mojom_name}}; > +{% endfor -%} > +}; > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ This should be } /* namespace {{ns}} */ Same in a few locations below. > +{% endfor %} > +{%- endif %} > +} /* 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..9328c7ca > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl > @@ -0,0 +1,238 @@ > +{#- 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 <libcamera/ipa/ipa_proxy_{{module_name}}.h> > + > +#include <vector> > + > +#include <libcamera/ipa/ipa_module_info.h> > +#include <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > + > +#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" YOu got the alphabetical order nearly right :-) > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(IPAProxy) > + > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) > + : IPAProxy(ipam), running_(false), > + isolate_(isolate) > +{ > + LOG(IPAProxy, Debug) > + << "initializing {{module_name}} proxy: loading IPA from " > + << ipam->path(); > + > + if (isolate_) { > + const std::string proxyWorkerPath = resolvePath("ipa_proxy_{{module_name}}"); > + if (proxyWorkerPath.empty()) { > + LOG(IPAProxy, Error) > + << "Failed to get proxy worker path"; > + return; > + } > + > + ipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxyWorkerPath.c_str()); Line wrap maybe ? > + 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 %} I think we can assume there will always be events. An IPA module that only consumes data would be a bit useless, wouldn't it ? :-) > + > + 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)); Do we need a dynamic cast ? > + 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(static_cast<uint32_t>({{cmd_enum_name}}::Exit), {}, {}); > +} > + > +{% if interface_cb.methods|length > 0 %} > +void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds) How about calling this receiveMessage() ? > +{ > + if (data.size() < 8) { > + LOG(IPAProxy, Error) > + << "Didn't receive enough bytes to parse event"; > + return; > + } > + > + {{cmd_event_enum_name}} cmd = static_cast<{{cmd_event_enum_name}}>(( > + data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); > + > + /* Need to skip another 4 bytes for the sequence number. */ Is the protocol documented somewhere ? There's a bit of an imbalance in the API, with recvIPC() having to deal with unpacking the command and skipping the sequence number, and sendSync() and sendAsync() dealing with those internally. Could recvIPC() be called with the command and two spans instead ? > + std::vector<uint8_t>::iterator vec = data.begin() + 8; > + size_t dataSize = data.size() - 8; > + switch (cmd) { > +{%- for method in interface_cb.methods %} > + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { > + {{method.mojom_name}}IPC(vec, dataSize, fds); > + break; > + } > +{%- endfor %} > + default: > + LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(cmd); > + } > +} > +{%- 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<uint8_t> _ipcInputBuf; > +{%- if method|method_input_has_fd %} > + std::vector<int32_t> _ipcInputFds; > +{%- endif %} > +{%- endif %} > +{%- if has_output %} > + std::vector<uint8_t> _ipcOutputBuf; > +{%- if method|method_output_has_fd %} > + std::vector<int32_t> _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_enum_name + "::" + method.mojom_name|cap %} > +{% if method|is_async %} > + int ret = ipc_->sendAsync(static_cast<uint32_t>({{cmd}}), {{input_buf}}, {{fds_buf}}); > +{%- else %} > + int ret = ipc_->sendSync(static_cast<uint32_t>({{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 %} > + } > +{% 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<uint8_t>::iterator data, > + size_t dataSize, > + [[maybe_unused]] std::vector<int32_t> &fds) > +{ > +{%- for param in method.parameters %} > + {{param|name}} {{param.mojom_name}}; > +{%- endfor %} > +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} > + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); > +} > +{% endfor %} > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ > +{% endfor %} > +{%- endif %} > +} /* 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..3fb7192f > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl > @@ -0,0 +1,118 @@ > +{#- 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 <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > + > +#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 { > +{%- if has_namespace %} > +{% for ns in namespace %} > +namespace {{ns}} { > +{% endfor %} > +{%- endif %} > + > +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > + {{- ", " if not loop.last}} > +{%- endfor -%} > +> {{method.mojom_name}}; > +{% endfor %} > + > +private: > + void recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds); data and fds shouldn't be modified, let's make them const. I'd even go one step further, and make them Span<const uint8_t> and Span<int32_t>. > + > +{% 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<uint8_t>::iterator data, > + size_t dataSize, > + std::vector<int32_t> &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<IPAIPCUnixSocket> ipc_; > + > + ControlSerializer controlSerializer_; I wonder if it would make sense to move some of the fields to the IPAProxy class (and possible some of the functions too). That can be done on top in a separate series. > +}; > + > +{%- if has_namespace %} > +{% for ns in namespace|reverse %} > +} /* {{ns}} */ > +{% endfor %} > +{%- endif %} > +} /* 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..dca4f99d > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl > @@ -0,0 +1,187 @@ > +{#- 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 <algorithm> > +#include <iostream> > +#include <sys/types.h> > +#include <tuple> > +#include <unistd.h> > + > +#include <libcamera/event_dispatcher.h> > +#include <libcamera/ipa/ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > +#include <libcamera/logging.h> > + > +#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) > + > +{%- if has_namespace %} > +{% for ns in namespace -%} > +using namespace {{ns}}; > +{% endfor %} > +{%- endif %} > + > +struct CallData { > + IPCUnixSocket::Payload *response; > + bool done; > +}; This doesn't seem to be used. > + > +{{interface_name}} *ipa_; > +IPCUnixSocket socket_; > + > +ControlSerializer controlSerializer_; > + > +bool exit_ = false; I still think it would be nicer to move the data and functions to a class, with a single instance created in the main() function. > + > +void readyRead(IPCUnixSocket *socket) > +{ > + IPCUnixSocket::Payload _message, _response; > + int _retRecv = socket->receive(&_message); > + if (_retRecv) { > + LOG({{proxy_name}}Worker, Error) > + << "Receive message failed" << _retRecv; > + return; > + } > + > + uint32_t _cmdUint, _seq; > + std::tie(_cmdUint, _seq) = IPAIPCUnixSocket::readHeader(_message); I think there's some room to refactor the IPC-related classes. The payload and header should probably not be tied to the IPAIPCUnixSocket class for instance, as they should be independent of the underlying mechanism. I'll try to comment more on this topic when reviewing the corresponding patches. > + IPAIPCUnixSocket::eraseHeader(_message); > + > + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_cmdUint); > + > + switch (_cmd) { > + case {{cmd_enum_name}}::Exit: { > + exit_ = true; > + break; > + } > + > +{% for method in interface_main.methods %} > + case {{cmd_enum_name}}::{{method.mojom_name|cap}}: { > + {{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 %} > + IPAIPCUnixSocket::writeHeader(_response, _cmdUint, _seq); > +{%- if method|method_return_value != "void" %} > + std::vector<uint8_t> _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 %} > + default: > + LOG({{proxy_name}}Worker, Error) << "Unknown command " << _cmdUint; > + } > +} > + > +{% for method in interface_cb.methods %} > +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} > +{ > + IPCUnixSocket::Payload _message; > + > + IPAIPCUnixSocket::writeHeader(_message, static_cast<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), 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 Likely something we'll have to handle more dynamically, but it doesn't have to be done now. A \todo comment would be good though. > + > + if (argc < 3) { > + LOG({{proxy_name}}Worker, Error) > + << "Tried to start worker with no args"; > + return EXIT_FAILURE; > + } > + > + int fd = std::stoi(argv[2]); > + LOG({{proxy_name}}Worker, Info) > + << "Starting worker for IPA module " << argv[1] > + << " with IPC fd = " << fd; > + > + std::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]); > + if (!ipam->isValid() || !ipam->load()) { > + LOG({{proxy_name}}Worker, Error) > + << "IPAModule " << argv[1] << " isn't valid"; > + 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 */ Upgrade to what ? :-) > + EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); > + while (!exit_) > + dispatcher->processEvents(); > + > + delete ipa_; > + socket_.close(); > + > + return 0; > +} > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl > new file mode 100644 > index 00000000..675f9adf > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_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 <libcamera/ipa/{{module_name}}.h> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > + > +#include "libcamera/internal/control_serializer.h" > +#include "libcamera/internal/ipa_data_serializer.h" > + > +#include <tuple> > +#include <vector> These should go first. > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(IPADataSerializer) > +{% for struct in structs_nonempty %} > +template<> > +class IPADataSerializer<{{struct|name_full(namespace_str)}}> > +{ > +public: > +{{- serializer.serializer(struct, base_controls, namespace_str)}} > +{%- if struct|has_fd %} > +{{serializer.deserializer_fd(struct, namespace_str)}} > +{%- else %} > +{{serializer.deserializer_no_fd(struct, namespace_str)}} > +{%- 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..f6836034 > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl > @@ -0,0 +1,205 @@ > +{#- 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) -%} There's one occurrence of a call to this macro with two arguments only. Should suffix have a default of "" ? > +{{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<uint8_t> {{param.mojom_name}}Buf; > +{%- if param|has_fd %} > + std::vector<int32_t> {{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 %} > + appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size()); > +{%- if param|has_fd %} > + appendPOD<uint32_t>({{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 -%} I wonder if we could optimize this by avoiding the intermediate vectors, but that's a candidate for a later optimization. > + > + > +{# > + # \brief Deserialize a single object from data buffer and fd vector > + # > + # \param pointer True deserializes the object into a dereferenced pointer s/True deserializes/If true, deserialize/ ? Same in other locations below. > + # \param iter True treats \a buf as an iterator instead of a vector > + # \param data_size Variable that holds the size of the vector referenced by \a buf > + # > + # 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, iter, data_size) -%} > +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( > +{%- if not iter %} > + {{buf}}.begin() + {{param.mojom_name}}Start, > +{%- else %} > + {{buf}} + {{param.mojom_name}}Start, > +{%- endif %} Maybe {{buf}}{{- ".begin()" if not iter}} + {{param.mojom_name}}Start, ? > +{%- if loop.last and not iter %} > + {{buf}}.end() > +{%- elif not iter %} > + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > +{%- elif iter and loop.length == 1 %} > + {{buf}} + {{data_size}} > +{%- else %} > + {{buf}} + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > +{%- endif -%} I wonder if it would simplify the generated code if we standardized on Span<uint8_t>. Passing both the vector iterator and the size in separate arguments can more easily result in them becoming out of sync. I was thinking the Span<>::subspan() function could be quite helpful here, but it doesn't handle overflows (requesting a subspan with offset > size is ill-defined) :-S Not necessarily something to be addressed now, but do you think it would make sense ? And of course if it helps fixing some of the issues outlined elsewhere in this review, we could already switch to span. > +{{- "," 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. > + # \param iter True treats \a buf as an iterator instead of a vector > + # \param data_size Variable that holds the size of the vector referenced by \a buf > + # > + # 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 deserializing after IPC calls. > + #} > +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%} > +{% set ns = namespace(size_offset = 0) %} > +{%- if params|length > 1 %} > +{%- for param in params %} > + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} const ? > +{%- if iter -%} > +, {{buf}} + {{data_size}} > +{%- endif -%} > +); > + {%- set ns.size_offset = ns.size_offset + 4 %} > +{%- if param|has_fd %} > + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} const ? > +{%- if iter -%} > +, {{buf}} + {{data_size}} > +{%- endif -%} > +); > + {%- 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}}; const ? Same in a few locations below. > +{%- 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, iter, data_size)|indent(16, True)}} > + } > + {%- else %} > + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds, iter, data_size)|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..51dbeb0e > --- /dev/null > +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl > @@ -0,0 +1,280 @@ > +{#- 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}}) { Maybe if ({{dataSize}} < {{size}}) { ? > + LOG(IPADataSerializer, Error) > + << "Failed to deserialize {{name}}: not enough {{typename}}, expected " << "Failed to deserialize " << "{{name}}" << ": not enough {{typename}}, expected " This should allow the compiler to deduplicate strings. > + << ({{size}}) << ", got " << ({{dataSize}}); > + return ret; > + } > +{%- endmacro %} > + > + > +{# > + # \brief Serialize some field into return vector > + # > + # Generate code to serialize \a field into retData, 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, namespace, loop) %} > +{%- if field|is_pod or field|is_enum %} > + std::vector<uint8_t> {{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<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}}_); > + {%- endif %} > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > +{%- elif field|is_fd %} > + std::vector<uint8_t> {{field.mojom_name}}; > + std::vector<int32_t> {{field.mojom_name}}Fds; > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > +{%- elif field|is_controls %} > + if (data.{{field.mojom_name}}_.size() > 0) { > + std::vector<uint8_t> {{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); > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + } else { > + appendPOD<uint32_t>(retData, 0); > + } > +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} > + std::vector<uint8_t> {{field.mojom_name}}; > + {%- if field|has_fd %} > + std::vector<int32_t> {{field.mojom_name}}Fds; > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > + {%- else %} > + std::tie({{field.mojom_name}}, std::ignore) = > + {%- endif %} > + {%- if field|is_array or field|is_map %} > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); > + {%- elif field|is_str %} > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > + {%- else %} > + IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}_, cs); > + {%- endif %} > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > + {%- if field|has_fd %} > + appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size()); > + {%- endif %} > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > + {%- if field|has_fd %} > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > + {%- endif %} > +{%- else %} > + /* Unknown serialization for {{field.mojom_name}}. */ > +{%- endif %} > +{%- endmacro %} Same here, I think there's room for optimization if we could avoid the intermediate vectors. > + > + > +{# > + # \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, namespace, 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}})); Do we need the static_cast<>, doesn't IPADataSerializer<{{field|name}}>::deserialize() return a {{field|name}} ? > + {%- else %} > + ret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(IPADataSerializer<uint{{field|bit_width}}_t>::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 = readPOD<uint32_t>(m, 0, data.end()); const ? You call IPADataSerializer<{{field|name}}>::deserialize() above for POD types, and readPOD() here. Do we need the IPADataSerializer<> specializations for all the PODs, can't we call readPOD() directly ? > + {%- 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 or field|is_str %} > + {%- set field_size = 4 %} > + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} > + size_t {{field.mojom_name}}Size = readPOD<uint32_t>(m, 0, data.end()); const ? > + {%- 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 = readPOD<uint32_t>(m, 4, data.end()); const ? > + {{- 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' -%} I'm afraid you've got quite a few integer overflow issues here :-S > + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}} > + ret.{{field.mojom_name}}_ = > + {%- if field|is_str %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size); > + {%- elif field|has_fd and (field|is_array or field|is_map) %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > + {%- elif field|has_fd and (not (field|is_array or field|is_map)) %} > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > + {%- elif (not field|has_fd) and (field|is_array or field|is_map) %} > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > + {%- else %} > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > + {%- endif %} Can't we advance m after reading the size fields ? That would avoid all the error-prone calculation, and get rid of at least some of the integer overflow issues. > + {%- 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, namespace) %} > + static std::tuple<std::vector<uint8_t>, std::vector<int32_t>> > + serialize(const {{struct|name_full(namespace)}} &data, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> retData; > +{%- if struct|has_fd %} > + std::vector<int32_t> retFds; > +{%- endif %} > +{%- for field in struct.fields %} > +{{serializer_field(field, base_controls, namespace, loop)}} > +{%- endfor %} > +{% if struct|has_fd %} > + return {retData, retFds}; > +{%- else %} > + return {retData, {}}; > +{%- 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, namespace) %} > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t> &data, > + std::vector<int32_t> &fds, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + {{struct|name_full(namespace)}} ret; > + std::vector<uint8_t>::iterator m = data.begin(); > + std::vector<int32_t>::iterator n = fds.begin(); > + > + size_t dataSize = data.size(); > + size_t fdsSize = fds.size(); > +{%- for field in struct.fields -%} > +{{deserializer_field(field, namespace, loop)}} > +{%- endfor %} > + return ret; > + } > + > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t>::iterator dataBegin, > + std::vector<uint8_t>::iterator dataEnd, > + std::vector<int32_t>::iterator fdsBegin, > + std::vector<int32_t>::iterator fdsEnd, All these should const const_iterator. There are quite a few locations in the deserializer where the data and fds vectors or iterators are not const. Could you fix that ? > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> data(dataBegin, dataEnd); This creates a copy of the data, do we really need that ? I think you should turn this around, implement the deserialization in this function, and implement the previous function as a wrapper around this one. > + std::vector<int32_t> fds(fdsBegin, fdsEnd); > + return IPADataSerializer<{{struct|name_full(namespace)}}>::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, namespace) %} > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t> &data, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + {{struct|name_full(namespace)}} ret; > + std::vector<uint8_t>::iterator m = data.begin(); > + > + size_t dataSize = data.size(); > +{%- for field in struct.fields -%} > +{{deserializer_field(field, namespace, loop)}} > +{%- endfor %} > + return ret; > + } > + > + static {{struct|name_full(namespace)}} > + deserialize(std::vector<uint8_t>::iterator dataBegin, > + std::vector<uint8_t>::iterator dataEnd, > +{%- if struct|needs_control_serializer %} > + ControlSerializer *cs) > +{%- else %} > + [[maybe_unused]] ControlSerializer *cs = nullptr) > +{%- endif %} > + { > + std::vector<uint8_t> data(dataBegin, dataEnd); > + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data, cs); > + } > +{%- endmacro %}
Hi Laurent, Thank you for the review. On Thu, Nov 19, 2020 at 02:39:17PM +0200, Laurent Pinchart wrote: > Hi Paul, > > Thank you for the patch. > > On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > > 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 <paul.elder@ideasonboard.com> > > > > --- > > Changes in v4: > > For the non-template files: > > - rename IPA{pipeline_name}CallbackInterface to > > IPA{pipeline_name}EventInterface > > - to avoid the notion of "callback" and emphasize that it's an event > > - add support for strings in custom structs > > - add validation, that async methods must not have return values > > - it throws exception and isn't very clear though...? > > - rename controls to libcamera::{pipeline_name}::controls (controls is > > now lowercase) > > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > > - same for their corresponding template files > > For the template files: > > - fix spacing (now it's all {{var}} instead of some {{ var }}) > > - except if it's code, so code is still {{ code }} > > - move inclusion of corresponding header to first in the inclusion list > > - fix copy&paste errors > > - change snake_case to camelCase in the generated code > > - template code still uses snake_case > > - change the generated command enums to an enum class, and make it > > capitalized (instead of allcaps) > > - add length checks to recvIPC (in proxy) > > - fix some template spacing > > - don't use const for PODs in function/signal parameters > > - add the proper length checks to readPOD/appendPOD > > - the helper functions for reading and writing PODs to and from > > serialized data > > - rename readUInt/appendUInt to readPOD/appendPOD > > - add support for strings in custom structs > > > > Changes in v3: > > - add support for namespaces > > - fix enum assignment (used to have +1 for CMD applied to all enums) > > - use readHeader, writeHeader, and eraseHeader as static class functions > > of IPAIPCUnixSocket (in the proxy worker) > > - add requirement that base controls *must* be defined in > > libcamera::{pipeline_name}::Controls > > > > 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_ipa_interface.h.tmpl | 113 ++++ > > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > > .../module_ipa_proxy.h.tmpl | 118 +++++ > > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > > .../module_ipa_serializer.h.tmpl | 44 ++ > > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > > 8 files changed, 1673 insertions(+) > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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_ipa_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 > > Let's start with the generator. > > [snip] > > > diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py > > new file mode 100644 > > index 00000000..c4fbf9b3 > > --- /dev/null > > +++ b/utils/ipc/generators/mojom_libcamera_generator.py > > @@ -0,0 +1,488 @@ > > Missing SPDX header and copyright notice. > > > +'''Generates libcamera files from a mojom.Module.''' > > Comments in Python use # :-) > > > + > > +import argparse > > +import ast > > +import datetime > > +import contextlib > > +import os > > +import re > > +import shutil > > +import sys > > +import tempfile > > ast, contextlib, shutil, sys and tempfile don't seem to be used. > > > + > > +from jinja2 import contextfilter > > contextfilter isn't used either. > > > + > > +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) > > You could combine the two with > > return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1), module) > > and possibly optimize it further by pre-compiling the regular > expression. I'll let you decide if it's worth it. > > > + > > +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:] > > These two functions don't seem to be used. > > > + > > +def Capitalize(name): > > + return name[0].upper() + name[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'): > > Is there a need for the outer parentheses ? Or is this dictated by the > mojom coding style ? Same comment for several locations below. > > > + 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)] > > Should these be moved a little bit further down, after the functions > they call ? The code works fine, but it would create an easier to read > flow. > > > + > > +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} > > Maybe > > ret.update(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: > > You can drop the len() call, [] evaluates to False. What do you mean? It evaluates to something like this: [<mojom.generate.module.Parameter object at 0x7f232bdbb280>] So it checks how many parameters have fd. > > + 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: > > Same here. > > Shouldn't it be method.response_parameters instead of method.parameters > ? Yeah it should be. > > + 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 > > The increase code reuse, you could implement these functions as follows. > > > 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 MethodParamHasFd(parameters): > if [x for x in parameters if HasFd(x)]: > return True > return False > > def MethodInputHasFd(method): > return MethodParamHasFd(MethodParamInputs(method)) > > def MethodOutputHasFd(method): > return MethodParamHasFd(MethodParamOutputs(method)) > > > + > > +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 ''), > > No need for the outer parentheses. > > > + 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 > > s/callbacks/Events/ ? > > > + if re.match("^IPA.*EventInterface$", 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 IsStr(element): > > + return element.kind.spec == 's' > > + > > +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 (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): > > + return 'std::string' > > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): > > Is the hasattr() check needed ? If element has no kind attribute, I > would expect the previous two if's to raise an exception. I've added comments for each section, because this confuses even me... So the upper-level if is to catch struct fields and function parameters. This if catches PODs. And you're right, the hasattr() check isn't needed. I think it was a remnant of fiddling with it and python complaining that element has no attribute kind. > > + return _kind_to_cpp_type[element.kind] > > + return element.kind.mojom_name > > + if isinstance(element, mojom.EnumValue): > > Extra space after comma. > > > + 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] > > For my own education, what type of elements does this cover ? This covers PODs that are members of vectors/maps. I've tested each case, and removed the ones that I can't remember what they do. > > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): > > + return _kind_to_cpp_type[element.kind] > > + if (hasattr(element, 'spec')): > > + if (element.spec == 's'): > > + return 'std::string' > > + 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 GetFullNameForElement(element, namespace_str): > > + name = GetNameForElement(element) > > + if namespace_str == '': > > + return name > > + return f'{namespace_str}::{name}' > > + > > +def ValidateZeroLength(l, s, cap=True): > > + if l is None: > > + return > > + 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.*EventInterface", x.mojom_name)] > > + ValidateSingleLength(intf, 'main interface') > > + intf = intf[0] > > + > > + # Validate presence of callback interface > > s/callback/event/ > > > + cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] > > s/cb/event/ ? > > > + ValidateSingleLength(cb, 'event interface') > > + cb = cb[0] > > + > > + # Validate required main interface functions > > + 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()') > > + > > + # Validate that all async methods don't have return values > > + intf_methods_async = [x for x in intf.methods if IsAsync(x)] > > + for method in intf_methods_async: > > + ValidateZeroLength(method.response_parameters, > > + f'{method.mojom_name} response parameters', False) > > + > > + cb_methods_async = [x for x in cb.methods if IsAsync(x)] > > s/cb_method_async/event_methods_async/ > > > + for method in cb_methods_async: > > + ValidateZeroLength(method.response_parameters, > > + f'{method.mojom_name} response parameters', False) > > + > > +class Generator(generator.Generator): > > + > > + @staticmethod > > + def GetTemplatePrefix(): > > + return 'libcamera_templates' > > + > > + def GetFilters(self): > > + libcamera_filters = { > > + 'all_types': GetAllTypes, > > + 'bit_width': BitWidth, > > + 'cap': Capitalize, > > 'cap': str.capitalize, > > and drop the custom Capitalize function. The built-in capitalize function doesn't preserve camel-casing so it's not sufficient: >>> "camelCaseClass".capitalize() 'Camelcaseclass' >>> Capitalize("camelCaseClass") 'CamelCaseClass' > > + '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, > > + 'is_str': IsStr, > > + '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, > > + 'name_full': GetFullNameForElement, > > + 'needs_control_serializer': NeedsControlSerializer, > > + 'params_comma_sep': ParamsCommaSep, > > + 'with_default_values': WithDefaultValues, > > + 'with_fds': WithFds, > > + } > > + return libcamera_filters > > + > > + def _GetJinjaExports(self): > > Would it make sense to store the return value of > ModuleClassName(self.module) in a local variable instead of calling the > function several times below ? If self.module is constant during the > lifetime of the Generator (I think it is), you could even compute it in > the constructor and store it in a member variable. Yeah I did think that was a good idea. Hm, it doesn't work in the constructor, but it works in GenerateFiles. > > + return { > > + 'base_controls': '%s::controls' % ModuleClassName(self.module), > > + 'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module), > > + 'cmd_event_enum_name': '_%sEventCmd' % 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.mojom_namespace != '', > > + 'imports': self.module.imports, > > I don't see any mention of 'imports' in the templates. Is this used > internally by mojom and/or jinja, or is it unused ? It was probably copied from the mojom C++ generator. I think I was going to use it for something... but then importing ended up working in such a way where this is no longer needed. > > + 'interface_cb': self.module.interfaces[1], > > interface_event ? > > > + 'interface_main': self.module.interfaces[0], > > Is there a guarantee the interfaces will always be specified in this > order ? The code in ValidateInterfaces() finds interfaces based on their > name. Ah, there is indeed no such guarantee, just a undocumented convention. > > + 'interface_name': 'IPA%sInterface' % ModuleClassName(self.module), > > + 'ipc_name': 'IPAIPCUnixSocket', > > This seems unused. It will be used when we support different IPC mechanisms :) Wait nvm, it won't. I'll remove it. > > + 'kinds': self.module.kinds, > > + 'module': self.module, > > Same for those two (unless they are used elsewhere than in the > templates). > > > + 'module_name': ModuleName(self.module.path), > > + 'module_class_name': ModuleClassName(self.module), > > Ditto. > > > + 'namespace': self.module.mojom_namespace.split('.'), > > + 'namespace_str': self.module.mojom_namespace.replace('.', '::') if > > + self.module.mojom_namespace is not None else '', > > + 'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module), > > + 'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module), > > Same here. > > > + 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0], > > + 'year': datetime.datetime.now().year, > > + } > > + > > + > > + @UseJinja('module_ipa_interface.h.tmpl') > > + def _GenerateDataHeader(self): > > + return self._GetJinjaExports() > > + > > + @UseJinja('module_ipa_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) > > This seems unused. > > > + > > + 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) > > I'm sure we could avoid the manual unrolling of all this, using a > dictionary to store the template files and iterating over it to add > arguments to the parser and replacing the above code, but it's probably > not worth it as I don't expect this will change a lot. Thanks, Paul
Hi Laurent, Thank you for the review. On Thu, Nov 19, 2020 at 07:21:00PM +0200, Laurent Pinchart wrote: > Hi Paul, > > Thank you for the patch. > > On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > > 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 <paul.elder@ideasonboard.com> > > > > --- > > Changes in v4: > > For the non-template files: > > - rename IPA{pipeline_name}CallbackInterface to > > IPA{pipeline_name}EventInterface > > - to avoid the notion of "callback" and emphasize that it's an event > > - add support for strings in custom structs > > - add validation, that async methods must not have return values > > - it throws exception and isn't very clear though...? > > - rename controls to libcamera::{pipeline_name}::controls (controls is > > now lowercase) > > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > > - same for their corresponding template files > > For the template files: > > - fix spacing (now it's all {{var}} instead of some {{ var }}) > > - except if it's code, so code is still {{ code }} > > - move inclusion of corresponding header to first in the inclusion list > > - fix copy&paste errors > > - change snake_case to camelCase in the generated code > > - template code still uses snake_case > > - change the generated command enums to an enum class, and make it > > capitalized (instead of allcaps) > > - add length checks to recvIPC (in proxy) > > - fix some template spacing > > - don't use const for PODs in function/signal parameters > > - add the proper length checks to readPOD/appendPOD > > - the helper functions for reading and writing PODs to and from > > serialized data > > - rename readUInt/appendUInt to readPOD/appendPOD > > - add support for strings in custom structs > > > > Changes in v3: > > - add support for namespaces > > - fix enum assignment (used to have +1 for CMD applied to all enums) > > - use readHeader, writeHeader, and eraseHeader as static class functions > > of IPAIPCUnixSocket (in the proxy worker) > > - add requirement that base controls *must* be defined in > > libcamera::{pipeline_name}::Controls > > > > 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_ipa_interface.h.tmpl | 113 ++++ > > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > > .../module_ipa_proxy.h.tmpl | 118 +++++ > > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > > .../module_ipa_serializer.h.tmpl | 44 ++ > > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > > 8 files changed, 1673 insertions(+) > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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 > > The proxy .h and .cpp files are named ipa_proxy_{pipeline}.{h,cpp}, > while the other generated files are named {pipeline}_ipa_interface.h and > {pipeline}_ipa_serializer.h. Could you pick one naming convention, and > apply it to all files ? The .tmpl files should then follow the same > convention. Ah, yeah. I'll make them all {pipeline}_ipa_{proxy,proxy_worker,interface,serializer}.{h,cpp} > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_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 > > And now, for the templates. > > > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > > new file mode 100644 > > index 00000000..a470b99e > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > > @@ -0,0 +1,113 @@ > > +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) {{year}}, Google Inc. > > Coming back to the question of copyright on generated code. > > First of all, the template itself is a creative work, which should have > a copyright notice, with a fixed date. This should go to the comment at > the beginning of the file. > > The generated file is the result of a non-creative process that takes > creative work as input. As such, it is covered by the copyright of the > template, and the mojom file. Would it be difficult to extract the > copyright data of the .mojom file and add it here ? I'm thnking about > the following at the beginning of the template: > > {#- > # SPDX-License-Identifier: LGPL-2.1-or-later > # Copyright (C) 2020, Google Inc. > -#} > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > * Copyright (C) 2020, Google Inc. > * {{copyright}} > * > * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} > * > * This file is auto-generated. Do not edit. > */ > > with {{copyright}} being replaced with the copyright statement from the > .mojom file. Mojo won't give you the information, but if you pass the > template name to _GetJinjaExport(), it may not be too difficult to > extract the copyright line. > > This may not be worth it, so feel free to just have a fixed copyright > here: > > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > /* > * Copyright (C) 2020, Google Inc. > * > * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} > * > * This file is auto-generated. Do not edit. > */ Okay. I'll take the latter. > > + * > > + * {{module_name}}_ipa_interface.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 <libcamera/ipa/ipa_interface.h> > > +#include <libcamera/ipa/{{module_name}}.h> > > + > > +{% if has_map %}#include <map>{% endif %} > > +{% if has_array %}#include <vector>{% endif %} > > + > > +namespace libcamera { > > +{%- if has_namespace %} > > +{% for ns in namespace %} > > +namespace {{ns}} { > > +{% endfor %} > > +{%- endif %} > > + > > +enum class {{cmd_enum_name}} { > > + Exit = 0, > > +{%- for method in interface_main.methods %} > > + {{method.mojom_name|cap}} = {{loop.index}}, > > +{%- endfor %} > > +}; > > + > > +enum class {{cmd_event_enum_name}} { > > +{%- for method in interface_cb.methods %} > > + {{method.mojom_name|cap}} = {{loop.index}}, > > +{%- endfor %} > > +}; > > + > > +{% for enum in enums %} > > +enum {{enum.mojom_name}} { > > +{%- for field in enum.fields %} > > + {{field.mojom_name}} = {{field.numeric_value}}, > > +{%- 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}}() {} > > I think you can skip empty destructors. > > > + > > + {{struct.mojom_name}}( > > +{%- for field in struct.fields -%} > > +{{field|name}} {{field.mojom_name}}{{", " if not loop.last}} > > Should non-trivial parameters be passed by const reference instead of > value ? They should. > > +{%- 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 %} > > + > > +{#- > > +Any consts or #defines should be moved to the mojom file when possible. > > +If anything needs to be #included, then {{module_name}}.h needs to have the > > +#include. > > +#} > > +class {{interface_name}} : public IPAInterface > > +{ > > +public: > > + virtual ~{{interface_name}}() {} > > The base class has a virtual destructor already, so you can drop this > one. > > > +{% 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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > > + {{- ", " if not loop.last}} > > +{%- endfor -%} > > +> {{method.mojom_name}}; > > +{% endfor -%} > > +}; > > + > > +{%- if has_namespace %} > > +{% for ns in namespace|reverse %} > > +} /* {{ns}} */ > > This should be > > } /* namespace {{ns}} */ > > Same in a few locations below. > > > +{% endfor %} > > +{%- endif %} > > +} /* 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..9328c7ca > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl > > @@ -0,0 +1,238 @@ > > +{#- 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 <libcamera/ipa/ipa_proxy_{{module_name}}.h> > > + > > +#include <vector> > > + > > +#include <libcamera/ipa/ipa_module_info.h> > > +#include <libcamera/ipa/{{module_name}}.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > > + > > +#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" > > YOu got the alphabetical order nearly right :-) > > > + > > +namespace libcamera { > > + > > +LOG_DECLARE_CATEGORY(IPAProxy) > > + > > +{%- if has_namespace %} > > +{% for ns in namespace %} > > +namespace {{ns}} { > > +{% endfor %} > > +{%- endif %} > > + > > +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) > > + : IPAProxy(ipam), running_(false), > > + isolate_(isolate) > > +{ > > + LOG(IPAProxy, Debug) > > + << "initializing {{module_name}} proxy: loading IPA from " > > + << ipam->path(); > > + > > + if (isolate_) { > > + const std::string proxyWorkerPath = resolvePath("ipa_proxy_{{module_name}}"); > > + if (proxyWorkerPath.empty()) { > > + LOG(IPAProxy, Error) > > + << "Failed to get proxy worker path"; > > + return; > > + } > > + > > + ipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxyWorkerPath.c_str()); > > Line wrap maybe ? > > > + 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 %} > > I think we can assume there will always be events. An IPA module that > only consumes data would be a bit useless, wouldn't it ? :-) > > > + > > + 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)); > > Do we need a dynamic cast ? > Yeah. src/libcamera/proxy/raspberrypi_ipa_proxy.cpp:78:46: error: no matching function for call to ‘std::unique_ptr<libcamera::ipa::rpi::IPARPiInterface>::unique_ptr(libcamera::IPAInterface*&)’ 78 | ipa_ = std::unique_ptr<IPARPiInterface>(ipai); ipai is pointer to IPAInterface, but we need unique pointer to IPA{pipeline}Interface. > > + 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(static_cast<uint32_t>({{cmd_enum_name}}::Exit), {}, {}); > > +} > > + > > +{% if interface_cb.methods|length > 0 %} > > +void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds) > > How about calling this receiveMessage() ? Yeah. Is recvMessage fine? > > +{ > > + if (data.size() < 8) { > > + LOG(IPAProxy, Error) > > + << "Didn't receive enough bytes to parse event"; > > + return; > > + } > > + > > + {{cmd_event_enum_name}} cmd = static_cast<{{cmd_event_enum_name}}>(( > > + data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); > > + > > + /* Need to skip another 4 bytes for the sequence number. */ > > Is the protocol documented somewhere ? No... > There's a bit of an imbalance in the API, with recvIPC() having to deal > with unpacking the command and skipping the sequence number, and > sendSync() and sendAsync() dealing with those internally. Could > recvIPC() be called with the command and two spans instead ? Oh shoot, yeah that is imbalanced. > > + std::vector<uint8_t>::iterator vec = data.begin() + 8; > > + size_t dataSize = data.size() - 8; > > + switch (cmd) { > > +{%- for method in interface_cb.methods %} > > + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { > > + {{method.mojom_name}}IPC(vec, dataSize, fds); > > + break; > > + } > > +{%- endfor %} > > + default: > > + LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(cmd); > > + } > > +} > > +{%- 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<uint8_t> _ipcInputBuf; > > +{%- if method|method_input_has_fd %} > > + std::vector<int32_t> _ipcInputFds; > > +{%- endif %} > > +{%- endif %} > > +{%- if has_output %} > > + std::vector<uint8_t> _ipcOutputBuf; > > +{%- if method|method_output_has_fd %} > > + std::vector<int32_t> _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_enum_name + "::" + method.mojom_name|cap %} > > +{% if method|is_async %} > > + int ret = ipc_->sendAsync(static_cast<uint32_t>({{cmd}}), {{input_buf}}, {{fds_buf}}); > > +{%- else %} > > + int ret = ipc_->sendSync(static_cast<uint32_t>({{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 %} > > + } > > +{% 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<uint8_t>::iterator data, > > + size_t dataSize, > > + [[maybe_unused]] std::vector<int32_t> &fds) > > +{ > > +{%- for param in method.parameters %} > > + {{param|name}} {{param.mojom_name}}; > > +{%- endfor %} > > +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} > > + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); > > +} > > +{% endfor %} > > + > > +{%- if has_namespace %} > > +{% for ns in namespace|reverse %} > > +} /* {{ns}} */ > > +{% endfor %} > > +{%- endif %} > > +} /* 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..3fb7192f > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl > > @@ -0,0 +1,118 @@ > > +{#- 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 <libcamera/ipa/ipa_interface.h> > > +#include <libcamera/ipa/{{module_name}}.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > + > > +#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 { > > +{%- if has_namespace %} > > +{% for ns in namespace %} > > +namespace {{ns}} { > > +{% endfor %} > > +{%- endif %} > > + > > +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > > + {{- ", " if not loop.last}} > > +{%- endfor -%} > > +> {{method.mojom_name}}; > > +{% endfor %} > > + > > +private: > > + void recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds); > > data and fds shouldn't be modified, let's make them const. I'd even go > one step further, and make them Span<const uint8_t> and Span<int32_t>. I'll make them const for now. > > + > > +{% 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<uint8_t>::iterator data, > > + size_t dataSize, > > + std::vector<int32_t> &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<IPAIPCUnixSocket> ipc_; > > + > > + ControlSerializer controlSerializer_; > > I wonder if it would make sense to move some of the fields to the > IPAProxy class (and possible some of the functions too). That can be > done on top in a separate series. Yeah, probably. > > +}; > > + > > +{%- if has_namespace %} > > +{% for ns in namespace|reverse %} > > +} /* {{ns}} */ > > +{% endfor %} > > +{%- endif %} > > +} /* 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..dca4f99d > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl > > @@ -0,0 +1,187 @@ > > +{#- 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 <algorithm> > > +#include <iostream> > > +#include <sys/types.h> > > +#include <tuple> > > +#include <unistd.h> > > + > > +#include <libcamera/event_dispatcher.h> > > +#include <libcamera/ipa/ipa_interface.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > > +#include <libcamera/logging.h> > > + > > +#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) > > + > > +{%- if has_namespace %} > > +{% for ns in namespace -%} > > +using namespace {{ns}}; > > +{% endfor %} > > +{%- endif %} > > + > > +struct CallData { > > + IPCUnixSocket::Payload *response; > > + bool done; > > +}; > > This doesn't seem to be used. Ah indeed it's not. > > + > > +{{interface_name}} *ipa_; > > +IPCUnixSocket socket_; > > + > > +ControlSerializer controlSerializer_; > > + > > +bool exit_ = false; > > I still think it would be nicer to move the data and functions to a > class, with a single instance created in the main() function. Hm, okay. > > + > > +void readyRead(IPCUnixSocket *socket) > > +{ > > + IPCUnixSocket::Payload _message, _response; > > + int _retRecv = socket->receive(&_message); > > + if (_retRecv) { > > + LOG({{proxy_name}}Worker, Error) > > + << "Receive message failed" << _retRecv; > > + return; > > + } > > + > > + uint32_t _cmdUint, _seq; > > + std::tie(_cmdUint, _seq) = IPAIPCUnixSocket::readHeader(_message); > > I think there's some room to refactor the IPC-related classes. The > payload and header should probably not be tied to the IPAIPCUnixSocket > class for instance, as they should be independent of the underlying > mechanism. I'll try to comment more on this topic when reviewing the > corresponding patches. > > > + IPAIPCUnixSocket::eraseHeader(_message); > > + > > + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_cmdUint); > > + > > + switch (_cmd) { > > + case {{cmd_enum_name}}::Exit: { > > + exit_ = true; > > + break; > > + } > > + > > +{% for method in interface_main.methods %} > > + case {{cmd_enum_name}}::{{method.mojom_name|cap}}: { > > + {{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 %} > > + IPAIPCUnixSocket::writeHeader(_response, _cmdUint, _seq); > > +{%- if method|method_return_value != "void" %} > > + std::vector<uint8_t> _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 %} > > + default: > > + LOG({{proxy_name}}Worker, Error) << "Unknown command " << _cmdUint; > > + } > > +} > > + > > +{% for method in interface_cb.methods %} > > +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} > > +{ > > + IPCUnixSocket::Payload _message; > > + > > + IPAIPCUnixSocket::writeHeader(_message, static_cast<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), 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 > > Likely something we'll have to handle more dynamically, but it doesn't > have to be done now. A \todo comment would be good though. ack > > + > > + if (argc < 3) { > > + LOG({{proxy_name}}Worker, Error) > > + << "Tried to start worker with no args"; > > + return EXIT_FAILURE; > > + } > > + > > + int fd = std::stoi(argv[2]); > > + LOG({{proxy_name}}Worker, Info) > > + << "Starting worker for IPA module " << argv[1] > > + << " with IPC fd = " << fd; > > + > > + std::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]); > > + if (!ipam->isValid() || !ipam->load()) { > > + LOG({{proxy_name}}Worker, Error) > > + << "IPAModule " << argv[1] << " isn't valid"; > > + 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 */ > > Upgrade to what ? :-) I copied this from the old IPAProxyLinuxWorker :) > > + EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); > > + while (!exit_) > > + dispatcher->processEvents(); > > + > > + delete ipa_; > > + socket_.close(); > > + > > + return 0; > > +} > > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl > > new file mode 100644 > > index 00000000..675f9adf > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_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 <libcamera/ipa/{{module_name}}.h> > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > + > > +#include "libcamera/internal/control_serializer.h" > > +#include "libcamera/internal/ipa_data_serializer.h" > > + > > +#include <tuple> > > +#include <vector> > > These should go first. > > > + > > +namespace libcamera { > > + > > +LOG_DECLARE_CATEGORY(IPADataSerializer) > > +{% for struct in structs_nonempty %} > > +template<> > > +class IPADataSerializer<{{struct|name_full(namespace_str)}}> > > +{ > > +public: > > +{{- serializer.serializer(struct, base_controls, namespace_str)}} > > +{%- if struct|has_fd %} > > +{{serializer.deserializer_fd(struct, namespace_str)}} > > +{%- else %} > > +{{serializer.deserializer_no_fd(struct, namespace_str)}} > > +{%- 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..f6836034 > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl > > @@ -0,0 +1,205 @@ > > +{#- 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) -%} > > There's one occurrence of a call to this macro with two arguments only. > Should suffix have a default of "" ? That's the default default value of macro parameters in jinja, that's why I left it blank. > > +{{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<uint8_t> {{param.mojom_name}}Buf; > > +{%- if param|has_fd %} > > + std::vector<int32_t> {{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 %} > > + appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size()); > > +{%- if param|has_fd %} > > + appendPOD<uint32_t>({{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 -%} > > I wonder if we could optimize this by avoiding the intermediate vectors, > but that's a candidate for a later optimization. I'll add it to the todo list. > > + > > + > > +{# > > + # \brief Deserialize a single object from data buffer and fd vector > > + # > > + # \param pointer True deserializes the object into a dereferenced pointer > > s/True deserializes/If true, deserialize/ ? > > Same in other locations below. Yes. > > + # \param iter True treats \a buf as an iterator instead of a vector > > + # \param data_size Variable that holds the size of the vector referenced by \a buf > > + # > > + # 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, iter, data_size) -%} > > +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( > > +{%- if not iter %} > > + {{buf}}.begin() + {{param.mojom_name}}Start, > > +{%- else %} > > + {{buf}} + {{param.mojom_name}}Start, > > +{%- endif %} > > Maybe > > {{buf}}{{- ".begin()" if not iter}} + {{param.mojom_name}}Start, > > ? Yeah that's better. > > +{%- if loop.last and not iter %} > > + {{buf}}.end() > > +{%- elif not iter %} > > + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > > +{%- elif iter and loop.length == 1 %} > > + {{buf}} + {{data_size}} > > +{%- else %} > > + {{buf}} + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > > +{%- endif -%} > > I wonder if it would simplify the generated code if we standardized on > Span<uint8_t>. Passing both the vector iterator and the size in separate > arguments can more easily result in them becoming out of sync. I was > thinking the Span<>::subspan() function could be quite helpful here, but > it doesn't handle overflows (requesting a subspan with offset > size is > ill-defined) :-S > > Not necessarily something to be addressed now, but do you think it would > make sense ? And of course if it helps fixing some of the issues > outlined elsewhere in this review, we could already switch to span. It probably would make sense... but it'll be a pain to convert :/ > > +{{- "," 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. > > + # \param iter True treats \a buf as an iterator instead of a vector > > + # \param data_size Variable that holds the size of the vector referenced by \a buf > > + # > > + # 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 deserializing after IPC calls. > > + #} > > +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%} > > +{% set ns = namespace(size_offset = 0) %} > > +{%- if params|length > 1 %} > > +{%- for param in params %} > > + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > > const ? > > > +{%- if iter -%} > > +, {{buf}} + {{data_size}} > > +{%- endif -%} > > +); > > + {%- set ns.size_offset = ns.size_offset + 4 %} > > +{%- if param|has_fd %} > > + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > > const ? > > > +{%- if iter -%} > > +, {{buf}} + {{data_size}} > > +{%- endif -%} > > +); > > + {%- 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}}; > > const ? > > Same in a few locations below. Yes. > > +{%- 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, iter, data_size)|indent(16, True)}} > > + } > > + {%- else %} > > + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds, iter, data_size)|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..51dbeb0e > > --- /dev/null > > +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl > > @@ -0,0 +1,280 @@ > > +{#- 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}}) { > > > Maybe > if ({{dataSize}} < {{size}}) { > > ? > > > + LOG(IPADataSerializer, Error) > > + << "Failed to deserialize {{name}}: not enough {{typename}}, expected " > > << "Failed to deserialize " << "{{name}}" > << ": not enough {{typename}}, expected " > > This should allow the compiler to deduplicate strings. > > > + << ({{size}}) << ", got " << ({{dataSize}}); > > + return ret; > > + } > > +{%- endmacro %} > > + > > + > > +{# > > + # \brief Serialize some field into return vector > > + # > > + # Generate code to serialize \a field into retData, 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, namespace, loop) %} > > +{%- if field|is_pod or field|is_enum %} > > + std::vector<uint8_t> {{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<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}}_); > > + {%- endif %} > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > +{%- elif field|is_fd %} > > + std::vector<uint8_t> {{field.mojom_name}}; > > + std::vector<int32_t> {{field.mojom_name}}Fds; > > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > > +{%- elif field|is_controls %} > > + if (data.{{field.mojom_name}}_.size() > 0) { > > + std::vector<uint8_t> {{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); > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > + } else { > > + appendPOD<uint32_t>(retData, 0); > > + } > > +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} > > + std::vector<uint8_t> {{field.mojom_name}}; > > + {%- if field|has_fd %} > > + std::vector<int32_t> {{field.mojom_name}}Fds; > > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > > + {%- else %} > > + std::tie({{field.mojom_name}}, std::ignore) = > > + {%- endif %} > > + {%- if field|is_array or field|is_map %} > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); > > + {%- elif field|is_str %} > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > > + {%- else %} > > + IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}_, cs); > > + {%- endif %} > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > > + {%- if field|has_fd %} > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size()); > > + {%- endif %} > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > + {%- if field|has_fd %} > > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > > + {%- endif %} > > +{%- else %} > > + /* Unknown serialization for {{field.mojom_name}}. */ > > +{%- endif %} > > +{%- endmacro %} > > Same here, I think there's room for optimization if we could avoid the > intermediate vectors. > > > + > > + > > +{# > > + # \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, namespace, 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}})); > > Do we need the static_cast<>, doesn't > IPADataSerializer<{{field|name}}>::deserialize() return a {{field|name}} > ? Ah yes, this one doesn't need the static_cast. The next one does due to enums though. > > + {%- else %} > > + ret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(IPADataSerializer<uint{{field|bit_width}}_t>::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 = readPOD<uint32_t>(m, 0, data.end()); > > const ? > > You call IPADataSerializer<{{field|name}}>::deserialize() above for POD > types, and readPOD() here. Do we need the IPADataSerializer<> > specializations for all the PODs, can't we call readPOD() directly ? No we can't get rid of the POD specializations of IPADataSerializer, because the IPADataSerializer for vectors and maps needs them. So for serdesing fields that are PODs we use IPADataSerializer, and for serdesing metadata like field size, we use readPOD. > > + {%- 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 or field|is_str %} > > + {%- set field_size = 4 %} > > + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} > > + size_t {{field.mojom_name}}Size = readPOD<uint32_t>(m, 0, data.end()); > > const ? > > > + {%- 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 = readPOD<uint32_t>(m, 4, data.end()); > > const ? > > > + {{- 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' -%} > > I'm afraid you've got quite a few integer overflow issues here :-S :S > > + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}} > > + ret.{{field.mojom_name}}_ = > > + {%- if field|is_str %} > > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size); > > + {%- elif field|has_fd and (field|is_array or field|is_map) %} > > + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > > + {%- elif field|has_fd and (not (field|is_array or field|is_map)) %} > > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > > + {%- elif (not field|has_fd) and (field|is_array or field|is_map) %} > > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > > + {%- else %} > > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > > + {%- endif %} > > Can't we advance m after reading the size fields ? That would avoid all > the error-prone calculation, and get rid of at least some of the integer > overflow issues. Oh, right. > > + {%- 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, namespace) %} > > + static std::tuple<std::vector<uint8_t>, std::vector<int32_t>> > > + serialize(const {{struct|name_full(namespace)}} &data, > > +{%- if struct|needs_control_serializer %} > > + ControlSerializer *cs) > > +{%- else %} > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > +{%- endif %} > > + { > > + std::vector<uint8_t> retData; > > +{%- if struct|has_fd %} > > + std::vector<int32_t> retFds; > > +{%- endif %} > > +{%- for field in struct.fields %} > > +{{serializer_field(field, base_controls, namespace, loop)}} > > +{%- endfor %} > > +{% if struct|has_fd %} > > + return {retData, retFds}; > > +{%- else %} > > + return {retData, {}}; > > +{%- 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, namespace) %} > > + static {{struct|name_full(namespace)}} > > + deserialize(std::vector<uint8_t> &data, > > + std::vector<int32_t> &fds, > > +{%- if struct|needs_control_serializer %} > > + ControlSerializer *cs) > > +{%- else %} > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > +{%- endif %} > > + { > > + {{struct|name_full(namespace)}} ret; > > + std::vector<uint8_t>::iterator m = data.begin(); > > + std::vector<int32_t>::iterator n = fds.begin(); > > + > > + size_t dataSize = data.size(); > > + size_t fdsSize = fds.size(); > > +{%- for field in struct.fields -%} > > +{{deserializer_field(field, namespace, loop)}} > > +{%- endfor %} > > + return ret; > > + } > > + > > + static {{struct|name_full(namespace)}} > > + deserialize(std::vector<uint8_t>::iterator dataBegin, > > + std::vector<uint8_t>::iterator dataEnd, > > + std::vector<int32_t>::iterator fdsBegin, > > + std::vector<int32_t>::iterator fdsEnd, > > All these should const const_iterator. There are quite a few locations > in the deserializer where the data and fds vectors or iterators are not > const. Could you fix that ? Yeah, I'll fix all of that. > > +{%- if struct|needs_control_serializer %} > > + ControlSerializer *cs) > > +{%- else %} > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > +{%- endif %} > > + { > > + std::vector<uint8_t> data(dataBegin, dataEnd); > > This creates a copy of the data, do we really need that ? I think you > should turn this around, implement the deserialization in this function, > and implement the previous function as a wrapper around this one. Ah, okay. > > + std::vector<int32_t> fds(fdsBegin, fdsEnd); > > + return IPADataSerializer<{{struct|name_full(namespace)}}>::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, namespace) %} > > + static {{struct|name_full(namespace)}} > > + deserialize(std::vector<uint8_t> &data, > > +{%- if struct|needs_control_serializer %} > > + ControlSerializer *cs) > > +{%- else %} > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > +{%- endif %} > > + { > > + {{struct|name_full(namespace)}} ret; > > + std::vector<uint8_t>::iterator m = data.begin(); > > + > > + size_t dataSize = data.size(); > > +{%- for field in struct.fields -%} > > +{{deserializer_field(field, namespace, loop)}} > > +{%- endfor %} > > + return ret; > > + } > > + > > + static {{struct|name_full(namespace)}} > > + deserialize(std::vector<uint8_t>::iterator dataBegin, > > + std::vector<uint8_t>::iterator dataEnd, > > +{%- if struct|needs_control_serializer %} > > + ControlSerializer *cs) > > +{%- else %} > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > +{%- endif %} > > + { > > + std::vector<uint8_t> data(dataBegin, dataEnd); > > + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data, cs); > > + } > > +{%- endmacro %} Thanks, Paul
Hi Paul, On Mon, Nov 23, 2020 at 05:11:56PM +0900, paul.elder@ideasonboard.com wrote: > On Thu, Nov 19, 2020 at 02:39:17PM +0200, Laurent Pinchart wrote: > > On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > > > 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 <paul.elder@ideasonboard.com> > > > > > > --- > > > Changes in v4: > > > For the non-template files: > > > - rename IPA{pipeline_name}CallbackInterface to > > > IPA{pipeline_name}EventInterface > > > - to avoid the notion of "callback" and emphasize that it's an event > > > - add support for strings in custom structs > > > - add validation, that async methods must not have return values > > > - it throws exception and isn't very clear though...? > > > - rename controls to libcamera::{pipeline_name}::controls (controls is > > > now lowercase) > > > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > > > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > > > - same for their corresponding template files > > > For the template files: > > > - fix spacing (now it's all {{var}} instead of some {{ var }}) > > > - except if it's code, so code is still {{ code }} > > > - move inclusion of corresponding header to first in the inclusion list > > > - fix copy&paste errors > > > - change snake_case to camelCase in the generated code > > > - template code still uses snake_case > > > - change the generated command enums to an enum class, and make it > > > capitalized (instead of allcaps) > > > - add length checks to recvIPC (in proxy) > > > - fix some template spacing > > > - don't use const for PODs in function/signal parameters > > > - add the proper length checks to readPOD/appendPOD > > > - the helper functions for reading and writing PODs to and from > > > serialized data > > > - rename readUInt/appendUInt to readPOD/appendPOD > > > - add support for strings in custom structs > > > > > > Changes in v3: > > > - add support for namespaces > > > - fix enum assignment (used to have +1 for CMD applied to all enums) > > > - use readHeader, writeHeader, and eraseHeader as static class functions > > > of IPAIPCUnixSocket (in the proxy worker) > > > - add requirement that base controls *must* be defined in > > > libcamera::{pipeline_name}::Controls > > > > > > 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_ipa_interface.h.tmpl | 113 ++++ > > > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > > > .../module_ipa_proxy.h.tmpl | 118 +++++ > > > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > > > .../module_ipa_serializer.h.tmpl | 44 ++ > > > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > > > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > > > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > > > 8 files changed, 1673 insertions(+) > > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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_ipa_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 > > > > Let's start with the generator. > > > > [snip] > > > > > diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py > > > new file mode 100644 > > > index 00000000..c4fbf9b3 > > > --- /dev/null > > > +++ b/utils/ipc/generators/mojom_libcamera_generator.py > > > @@ -0,0 +1,488 @@ > > > > Missing SPDX header and copyright notice. > > > > > +'''Generates libcamera files from a mojom.Module.''' > > > > Comments in Python use # :-) > > > > > + > > > +import argparse > > > +import ast > > > +import datetime > > > +import contextlib > > > +import os > > > +import re > > > +import shutil > > > +import sys > > > +import tempfile > > > > ast, contextlib, shutil, sys and tempfile don't seem to be used. > > > > > + > > > +from jinja2 import contextfilter > > > > contextfilter isn't used either. > > > > > + > > > +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) > > > > You could combine the two with > > > > return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1), module) > > > > and possibly optimize it further by pre-compiling the regular > > expression. I'll let you decide if it's worth it. > > > > > + > > > +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:] > > > > These two functions don't seem to be used. > > > > > + > > > +def Capitalize(name): > > > + return name[0].upper() + name[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'): > > > > Is there a need for the outer parentheses ? Or is this dictated by the > > mojom coding style ? Same comment for several locations below. > > > > > + 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)] > > > > Should these be moved a little bit further down, after the functions > > they call ? The code works fine, but it would create an easier to read > > flow. > > > > > + > > > +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} > > > > Maybe > > > > ret.update(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: > > > > You can drop the len() call, [] evaluates to False. > > What do you mean? It evaluates to something like this: > > [<mojom.generate.module.Parameter object at 0x7f232bdbb280>] > > So it checks how many parameters have fd. My bad. I thought python would make it a list, not a generator, and empty lists evaluate to False. Please ignore this comment. It would be nice if python had a syntax similar to if one of HasFd(x) for x in method.parameters: :-) > > > + 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: > > > > Same here. > > > > Shouldn't it be method.response_parameters instead of method.parameters > > ? > > Yeah it should be. > > > > + 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 > > > > The increase code reuse, you could implement these functions as follows. > > > > > > 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 MethodParamHasFd(parameters): > > if [x for x in parameters if HasFd(x)]: > > return True > > return False > > > > def MethodInputHasFd(method): > > return MethodParamHasFd(MethodParamInputs(method)) > > > > def MethodOutputHasFd(method): > > return MethodParamHasFd(MethodParamOutputs(method)) > > > > > + > > > +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 ''), > > > > No need for the outer parentheses. > > > > > + 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 > > > > s/callbacks/Events/ ? > > > > > + if re.match("^IPA.*EventInterface$", 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 IsStr(element): > > > + return element.kind.spec == 's' > > > + > > > +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 (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): > > > + return 'std::string' > > > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): > > > > Is the hasattr() check needed ? If element has no kind attribute, I > > would expect the previous two if's to raise an exception. > > I've added comments for each section, because this confuses even me... > > So the upper-level if is to catch struct fields and function parameters. > This if catches PODs. > > And you're right, the hasattr() check isn't needed. I think it was a > remnant of fiddling with it and python complaining that element has no > attribute kind. > > > > + return _kind_to_cpp_type[element.kind] > > > + return element.kind.mojom_name > > > + if isinstance(element, mojom.EnumValue): > > > > Extra space after comma. > > > > > + 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] > > > > For my own education, what type of elements does this cover ? > > This covers PODs that are members of vectors/maps. > > I've tested each case, and removed the ones that I can't remember what > they do. Thank you, that will make the code easier to maintain. > > > + if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type): > > > + return _kind_to_cpp_type[element.kind] > > > + if (hasattr(element, 'spec')): > > > + if (element.spec == 's'): > > > + return 'std::string' > > > + 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 GetFullNameForElement(element, namespace_str): > > > + name = GetNameForElement(element) > > > + if namespace_str == '': > > > + return name > > > + return f'{namespace_str}::{name}' > > > + > > > +def ValidateZeroLength(l, s, cap=True): > > > + if l is None: > > > + return > > > + 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.*EventInterface", x.mojom_name)] > > > + ValidateSingleLength(intf, 'main interface') > > > + intf = intf[0] > > > + > > > + # Validate presence of callback interface > > > > s/callback/event/ > > > > > + cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] > > > > s/cb/event/ ? > > > > > + ValidateSingleLength(cb, 'event interface') > > > + cb = cb[0] > > > + > > > + # Validate required main interface functions > > > + 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()') > > > + > > > + # Validate that all async methods don't have return values > > > + intf_methods_async = [x for x in intf.methods if IsAsync(x)] > > > + for method in intf_methods_async: > > > + ValidateZeroLength(method.response_parameters, > > > + f'{method.mojom_name} response parameters', False) > > > + > > > + cb_methods_async = [x for x in cb.methods if IsAsync(x)] > > > > s/cb_method_async/event_methods_async/ > > > > > + for method in cb_methods_async: > > > + ValidateZeroLength(method.response_parameters, > > > + f'{method.mojom_name} response parameters', False) > > > + > > > +class Generator(generator.Generator): > > > + > > > + @staticmethod > > > + def GetTemplatePrefix(): > > > + return 'libcamera_templates' > > > + > > > + def GetFilters(self): > > > + libcamera_filters = { > > > + 'all_types': GetAllTypes, > > > + 'bit_width': BitWidth, > > > + 'cap': Capitalize, > > > > 'cap': str.capitalize, > > > > and drop the custom Capitalize function. > > The built-in capitalize function doesn't preserve camel-casing so it's > not sufficient: > > >>> "camelCaseClass".capitalize() > 'Camelcaseclass' > >>> Capitalize("camelCaseClass") > 'CamelCaseClass' Oops. > > > + '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, > > > + 'is_str': IsStr, > > > + '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, > > > + 'name_full': GetFullNameForElement, > > > + 'needs_control_serializer': NeedsControlSerializer, > > > + 'params_comma_sep': ParamsCommaSep, > > > + 'with_default_values': WithDefaultValues, > > > + 'with_fds': WithFds, > > > + } > > > + return libcamera_filters > > > + > > > + def _GetJinjaExports(self): > > > > Would it make sense to store the return value of > > ModuleClassName(self.module) in a local variable instead of calling the > > function several times below ? If self.module is constant during the > > lifetime of the Generator (I think it is), you could even compute it in > > the constructor and store it in a member variable. > > Yeah I did think that was a good idea. > > Hm, it doesn't work in the constructor, but it works in GenerateFiles. > > > > + return { > > > + 'base_controls': '%s::controls' % ModuleClassName(self.module), > > > + 'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module), > > > + 'cmd_event_enum_name': '_%sEventCmd' % 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.mojom_namespace != '', > > > + 'imports': self.module.imports, > > > > I don't see any mention of 'imports' in the templates. Is this used > > internally by mojom and/or jinja, or is it unused ? > > It was probably copied from the mojom C++ generator. I think I was going > to use it for something... but then importing ended up working in such a > way where this is no longer needed. > > > > + 'interface_cb': self.module.interfaces[1], > > > > interface_event ? > > > > > + 'interface_main': self.module.interfaces[0], > > > > Is there a guarantee the interfaces will always be specified in this > > order ? The code in ValidateInterfaces() finds interfaces based on their > > name. > > Ah, there is indeed no such guarantee, just a undocumented convention. Could we then either avoid relying on this, or document the convention ? :-) > > > + 'interface_name': 'IPA%sInterface' % ModuleClassName(self.module), > > > + 'ipc_name': 'IPAIPCUnixSocket', > > > > This seems unused. > > It will be used when we support different IPC mechanisms :) > > Wait nvm, it won't. I'll remove it. > > > > + 'kinds': self.module.kinds, > > > + 'module': self.module, > > > > Same for those two (unless they are used elsewhere than in the > > templates). > > > > > + 'module_name': ModuleName(self.module.path), > > > + 'module_class_name': ModuleClassName(self.module), > > > > Ditto. > > > > > + 'namespace': self.module.mojom_namespace.split('.'), > > > + 'namespace_str': self.module.mojom_namespace.replace('.', '::') if > > > + self.module.mojom_namespace is not None else '', > > > + 'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module), > > > + 'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module), > > > > Same here. > > > > > + 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0], > > > + 'year': datetime.datetime.now().year, > > > + } > > > + > > > + > > > + @UseJinja('module_ipa_interface.h.tmpl') > > > + def _GenerateDataHeader(self): > > > + return self._GetJinjaExports() > > > + > > > + @UseJinja('module_ipa_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) > > > > This seems unused. > > > > > + > > > + 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) > > > > I'm sure we could avoid the manual unrolling of all this, using a > > dictionary to store the template files and iterating over it to add > > arguments to the parser and replacing the above code, but it's probably > > not worth it as I don't expect this will change a lot.
Hi Paul, On Thu, Nov 26, 2020 at 03:49:18PM +0900, paul.elder@ideasonboard.com wrote: > On Thu, Nov 19, 2020 at 07:21:00PM +0200, Laurent Pinchart wrote: > > On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote: > > > 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 <paul.elder@ideasonboard.com> > > > > > > --- > > > Changes in v4: > > > For the non-template files: > > > - rename IPA{pipeline_name}CallbackInterface to > > > IPA{pipeline_name}EventInterface > > > - to avoid the notion of "callback" and emphasize that it's an event > > > - add support for strings in custom structs > > > - add validation, that async methods must not have return values > > > - it throws exception and isn't very clear though...? > > > - rename controls to libcamera::{pipeline_name}::controls (controls is > > > now lowercase) > > > - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, > > > and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h > > > - same for their corresponding template files > > > For the template files: > > > - fix spacing (now it's all {{var}} instead of some {{ var }}) > > > - except if it's code, so code is still {{ code }} > > > - move inclusion of corresponding header to first in the inclusion list > > > - fix copy&paste errors > > > - change snake_case to camelCase in the generated code > > > - template code still uses snake_case > > > - change the generated command enums to an enum class, and make it > > > capitalized (instead of allcaps) > > > - add length checks to recvIPC (in proxy) > > > - fix some template spacing > > > - don't use const for PODs in function/signal parameters > > > - add the proper length checks to readPOD/appendPOD > > > - the helper functions for reading and writing PODs to and from > > > serialized data > > > - rename readUInt/appendUInt to readPOD/appendPOD > > > - add support for strings in custom structs > > > > > > Changes in v3: > > > - add support for namespaces > > > - fix enum assignment (used to have +1 for CMD applied to all enums) > > > - use readHeader, writeHeader, and eraseHeader as static class functions > > > of IPAIPCUnixSocket (in the proxy worker) > > > - add requirement that base controls *must* be defined in > > > libcamera::{pipeline_name}::Controls > > > > > > 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_ipa_interface.h.tmpl | 113 ++++ > > > .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ > > > .../module_ipa_proxy.h.tmpl | 118 +++++ > > > .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ > > > .../module_ipa_serializer.h.tmpl | 44 ++ > > > .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ > > > .../libcamera_templates/serializer.tmpl | 280 ++++++++++ > > > .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ > > > 8 files changed, 1673 insertions(+) > > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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 > > > > The proxy .h and .cpp files are named ipa_proxy_{pipeline}.{h,cpp}, > > while the other generated files are named {pipeline}_ipa_interface.h and > > {pipeline}_ipa_serializer.h. Could you pick one naming convention, and > > apply it to all files ? The .tmpl files should then follow the same > > convention. > > Ah, yeah. I'll make them all {pipeline}_ipa_{proxy,proxy_worker,interface,serializer}.{h,cpp} > > > > create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_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 > > > > And now, for the templates. > > > > > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > > > new file mode 100644 > > > index 00000000..a470b99e > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl > > > @@ -0,0 +1,113 @@ > > > +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > > +/* > > > + * Copyright (C) {{year}}, Google Inc. > > > > Coming back to the question of copyright on generated code. > > > > First of all, the template itself is a creative work, which should have > > a copyright notice, with a fixed date. This should go to the comment at > > the beginning of the file. > > > > The generated file is the result of a non-creative process that takes > > creative work as input. As such, it is covered by the copyright of the > > template, and the mojom file. Would it be difficult to extract the > > copyright data of the .mojom file and add it here ? I'm thnking about > > the following at the beginning of the template: > > > > {#- > > # SPDX-License-Identifier: LGPL-2.1-or-later > > # Copyright (C) 2020, Google Inc. > > -#} > > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > > /* > > * Copyright (C) 2020, Google Inc. > > * {{copyright}} > > * > > * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} > > * > > * This file is auto-generated. Do not edit. > > */ > > > > with {{copyright}} being replaced with the copyright statement from the > > .mojom file. Mojo won't give you the information, but if you pass the > > template name to _GetJinjaExport(), it may not be too difficult to > > extract the copyright line. > > > > This may not be worth it, so feel free to just have a fixed copyright > > here: > > > > /* SPDX-License-Identifier: LGPL-2.1-or-later */ > > /* > > * Copyright (C) 2020, Google Inc. > > * > > * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} > > * > > * This file is auto-generated. Do not edit. > > */ > > Okay. I'll take the latter. > > > > + * > > > + * {{module_name}}_ipa_interface.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 <libcamera/ipa/ipa_interface.h> > > > +#include <libcamera/ipa/{{module_name}}.h> > > > + > > > +{% if has_map %}#include <map>{% endif %} > > > +{% if has_array %}#include <vector>{% endif %} > > > + > > > +namespace libcamera { > > > +{%- if has_namespace %} > > > +{% for ns in namespace %} > > > +namespace {{ns}} { > > > +{% endfor %} > > > +{%- endif %} > > > + > > > +enum class {{cmd_enum_name}} { > > > + Exit = 0, > > > +{%- for method in interface_main.methods %} > > > + {{method.mojom_name|cap}} = {{loop.index}}, > > > +{%- endfor %} > > > +}; > > > + > > > +enum class {{cmd_event_enum_name}} { > > > +{%- for method in interface_cb.methods %} > > > + {{method.mojom_name|cap}} = {{loop.index}}, > > > +{%- endfor %} > > > +}; > > > + > > > +{% for enum in enums %} > > > +enum {{enum.mojom_name}} { > > > +{%- for field in enum.fields %} > > > + {{field.mojom_name}} = {{field.numeric_value}}, > > > +{%- 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}}() {} > > > > I think you can skip empty destructors. > > > > > + > > > + {{struct.mojom_name}}( > > > +{%- for field in struct.fields -%} > > > +{{field|name}} {{field.mojom_name}}{{", " if not loop.last}} > > > > Should non-trivial parameters be passed by const reference instead of > > value ? > > They should. > > > > +{%- 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 %} > > > + > > > +{#- > > > +Any consts or #defines should be moved to the mojom file when possible. > > > +If anything needs to be #included, then {{module_name}}.h needs to have the > > > +#include. > > > +#} > > > +class {{interface_name}} : public IPAInterface > > > +{ > > > +public: > > > + virtual ~{{interface_name}}() {} > > > > The base class has a virtual destructor already, so you can drop this > > one. > > > > > +{% 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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > > > + {{- ", " if not loop.last}} > > > +{%- endfor -%} > > > +> {{method.mojom_name}}; > > > +{% endfor -%} > > > +}; > > > + > > > +{%- if has_namespace %} > > > +{% for ns in namespace|reverse %} > > > +} /* {{ns}} */ > > > > This should be > > > > } /* namespace {{ns}} */ > > > > Same in a few locations below. > > > > > +{% endfor %} > > > +{%- endif %} > > > +} /* 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..9328c7ca > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl > > > @@ -0,0 +1,238 @@ > > > +{#- 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 <libcamera/ipa/ipa_proxy_{{module_name}}.h> > > > + > > > +#include <vector> > > > + > > > +#include <libcamera/ipa/ipa_module_info.h> > > > +#include <libcamera/ipa/{{module_name}}.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > > > + > > > +#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" > > > > YOu got the alphabetical order nearly right :-) > > > > > + > > > +namespace libcamera { > > > + > > > +LOG_DECLARE_CATEGORY(IPAProxy) > > > + > > > +{%- if has_namespace %} > > > +{% for ns in namespace %} > > > +namespace {{ns}} { > > > +{% endfor %} > > > +{%- endif %} > > > + > > > +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) > > > + : IPAProxy(ipam), running_(false), > > > + isolate_(isolate) > > > +{ > > > + LOG(IPAProxy, Debug) > > > + << "initializing {{module_name}} proxy: loading IPA from " > > > + << ipam->path(); > > > + > > > + if (isolate_) { > > > + const std::string proxyWorkerPath = resolvePath("ipa_proxy_{{module_name}}"); > > > + if (proxyWorkerPath.empty()) { > > > + LOG(IPAProxy, Error) > > > + << "Failed to get proxy worker path"; > > > + return; > > > + } > > > + > > > + ipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxyWorkerPath.c_str()); > > > > Line wrap maybe ? > > > > > + 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 %} > > > > I think we can assume there will always be events. An IPA module that > > only consumes data would be a bit useless, wouldn't it ? :-) > > > > > + > > > + 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)); > > > > Do we need a dynamic cast ? > > Yeah. > > src/libcamera/proxy/raspberrypi_ipa_proxy.cpp:78:46: error: no matching function for call to > ‘std::unique_ptr<libcamera::ipa::rpi::IPARPiInterface>::unique_ptr(libcamera::IPAInterface*&)’ > 78 | ipa_ = std::unique_ptr<IPARPiInterface>(ipai); > > ipai is pointer to IPAInterface, but we need unique pointer to IPA{pipeline}Interface. I mean, can't you use a static_cast ? > > > + 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(static_cast<uint32_t>({{cmd_enum_name}}::Exit), {}, {}); > > > +} > > > + > > > +{% if interface_cb.methods|length > 0 %} > > > +void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds) > > > > How about calling this receiveMessage() ? > > Yeah. Is recvMessage fine? We usually try to avoid abbreviations when possible, but if receiveMessage() is too long, exceptions are allowed. > > > +{ > > > + if (data.size() < 8) { > > > + LOG(IPAProxy, Error) > > > + << "Didn't receive enough bytes to parse event"; > > > + return; > > > + } > > > + > > > + {{cmd_event_enum_name}} cmd = static_cast<{{cmd_event_enum_name}}>(( > > > + data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); > > > + > > > + /* Need to skip another 4 bytes for the sequence number. */ > > > > Is the protocol documented somewhere ? > > No... I think this should be fixed, but I also wonder if we shouldn't use the same protocol as mojo. Documentation would mostly be free then :-) It's something that can be done on top, can you record it in a \todo ? > > There's a bit of an imbalance in the API, with recvIPC() having to deal > > with unpacking the command and skipping the sequence number, and > > sendSync() and sendAsync() dealing with those internally. Could > > recvIPC() be called with the command and two spans instead ? > > Oh shoot, yeah that is imbalanced. > > > > + std::vector<uint8_t>::iterator vec = data.begin() + 8; > > > + size_t dataSize = data.size() - 8; > > > + switch (cmd) { > > > +{%- for method in interface_cb.methods %} > > > + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { > > > + {{method.mojom_name}}IPC(vec, dataSize, fds); > > > + break; > > > + } > > > +{%- endfor %} > > > + default: > > > + LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(cmd); > > > + } > > > +} > > > +{%- 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<uint8_t> _ipcInputBuf; > > > +{%- if method|method_input_has_fd %} > > > + std::vector<int32_t> _ipcInputFds; > > > +{%- endif %} > > > +{%- endif %} > > > +{%- if has_output %} > > > + std::vector<uint8_t> _ipcOutputBuf; > > > +{%- if method|method_output_has_fd %} > > > + std::vector<int32_t> _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_enum_name + "::" + method.mojom_name|cap %} > > > +{% if method|is_async %} > > > + int ret = ipc_->sendAsync(static_cast<uint32_t>({{cmd}}), {{input_buf}}, {{fds_buf}}); > > > +{%- else %} > > > + int ret = ipc_->sendSync(static_cast<uint32_t>({{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 %} > > > + } > > > +{% 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<uint8_t>::iterator data, > > > + size_t dataSize, > > > + [[maybe_unused]] std::vector<int32_t> &fds) > > > +{ > > > +{%- for param in method.parameters %} > > > + {{param|name}} {{param.mojom_name}}; > > > +{%- endfor %} > > > +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} > > > + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); > > > +} > > > +{% endfor %} > > > + > > > +{%- if has_namespace %} > > > +{% for ns in namespace|reverse %} > > > +} /* {{ns}} */ > > > +{% endfor %} > > > +{%- endif %} > > > +} /* 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..3fb7192f > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl > > > @@ -0,0 +1,118 @@ > > > +{#- 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 <libcamera/ipa/ipa_interface.h> > > > +#include <libcamera/ipa/{{module_name}}.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > > + > > > +#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 { > > > +{%- if has_namespace %} > > > +{% for ns in namespace %} > > > +namespace {{ns}} { > > > +{% endfor %} > > > +{%- endif %} > > > + > > > +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} > > > + {{- ", " if not loop.last}} > > > +{%- endfor -%} > > > +> {{method.mojom_name}}; > > > +{% endfor %} > > > + > > > +private: > > > + void recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds); > > > > data and fds shouldn't be modified, let's make them const. I'd even go > > one step further, and make them Span<const uint8_t> and Span<int32_t>. > > I'll make them const for now. > > > > + > > > +{% 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<uint8_t>::iterator data, > > > + size_t dataSize, > > > + std::vector<int32_t> &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<IPAIPCUnixSocket> ipc_; > > > + > > > + ControlSerializer controlSerializer_; > > > > I wonder if it would make sense to move some of the fields to the > > IPAProxy class (and possible some of the functions too). That can be > > done on top in a separate series. > > Yeah, probably. > > > > +}; > > > + > > > +{%- if has_namespace %} > > > +{% for ns in namespace|reverse %} > > > +} /* {{ns}} */ > > > +{% endfor %} > > > +{%- endif %} > > > +} /* 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..dca4f99d > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl > > > @@ -0,0 +1,187 @@ > > > +{#- 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 <algorithm> > > > +#include <iostream> > > > +#include <sys/types.h> > > > +#include <tuple> > > > +#include <unistd.h> > > > + > > > +#include <libcamera/event_dispatcher.h> > > > +#include <libcamera/ipa/ipa_interface.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> > > > +#include <libcamera/logging.h> > > > + > > > +#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) > > > + > > > +{%- if has_namespace %} > > > +{% for ns in namespace -%} > > > +using namespace {{ns}}; > > > +{% endfor %} > > > +{%- endif %} > > > + > > > +struct CallData { > > > + IPCUnixSocket::Payload *response; > > > + bool done; > > > +}; > > > > This doesn't seem to be used. > > Ah indeed it's not. > > > > + > > > +{{interface_name}} *ipa_; > > > +IPCUnixSocket socket_; > > > + > > > +ControlSerializer controlSerializer_; > > > + > > > +bool exit_ = false; > > > > I still think it would be nicer to move the data and functions to a > > class, with a single instance created in the main() function. > > Hm, okay. > > > > + > > > +void readyRead(IPCUnixSocket *socket) > > > +{ > > > + IPCUnixSocket::Payload _message, _response; > > > + int _retRecv = socket->receive(&_message); > > > + if (_retRecv) { > > > + LOG({{proxy_name}}Worker, Error) > > > + << "Receive message failed" << _retRecv; > > > + return; > > > + } > > > + > > > + uint32_t _cmdUint, _seq; > > > + std::tie(_cmdUint, _seq) = IPAIPCUnixSocket::readHeader(_message); > > > > I think there's some room to refactor the IPC-related classes. The > > payload and header should probably not be tied to the IPAIPCUnixSocket > > class for instance, as they should be independent of the underlying > > mechanism. I'll try to comment more on this topic when reviewing the > > corresponding patches. > > > > > + IPAIPCUnixSocket::eraseHeader(_message); > > > + > > > + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_cmdUint); > > > + > > > + switch (_cmd) { > > > + case {{cmd_enum_name}}::Exit: { > > > + exit_ = true; > > > + break; > > > + } > > > + > > > +{% for method in interface_main.methods %} > > > + case {{cmd_enum_name}}::{{method.mojom_name|cap}}: { > > > + {{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 %} > > > + IPAIPCUnixSocket::writeHeader(_response, _cmdUint, _seq); > > > +{%- if method|method_return_value != "void" %} > > > + std::vector<uint8_t> _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 %} > > > + default: > > > + LOG({{proxy_name}}Worker, Error) << "Unknown command " << _cmdUint; > > > + } > > > +} > > > + > > > +{% for method in interface_cb.methods %} > > > +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} > > > +{ > > > + IPCUnixSocket::Payload _message; > > > + > > > + IPAIPCUnixSocket::writeHeader(_message, static_cast<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), 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 > > > > Likely something we'll have to handle more dynamically, but it doesn't > > have to be done now. A \todo comment would be good though. > > ack > > > > + > > > + if (argc < 3) { > > > + LOG({{proxy_name}}Worker, Error) > > > + << "Tried to start worker with no args"; > > > + return EXIT_FAILURE; > > > + } > > > + > > > + int fd = std::stoi(argv[2]); > > > + LOG({{proxy_name}}Worker, Info) > > > + << "Starting worker for IPA module " << argv[1] > > > + << " with IPC fd = " << fd; > > > + > > > + std::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]); > > > + if (!ipam->isValid() || !ipam->load()) { > > > + LOG({{proxy_name}}Worker, Error) > > > + << "IPAModule " << argv[1] << " isn't valid"; > > > + 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 */ > > > > Upgrade to what ? :-) > > I copied this from the old IPAProxyLinuxWorker :) Maybe it's not needed anymore, if nobody remembers what it is ? :-) > > > + EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); > > > + while (!exit_) > > > + dispatcher->processEvents(); > > > + > > > + delete ipa_; > > > + socket_.close(); > > > + > > > + return 0; > > > +} > > > diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl > > > new file mode 100644 > > > index 00000000..675f9adf > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_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 <libcamera/ipa/{{module_name}}.h> > > > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> > > > + > > > +#include "libcamera/internal/control_serializer.h" > > > +#include "libcamera/internal/ipa_data_serializer.h" > > > + > > > +#include <tuple> > > > +#include <vector> > > > > These should go first. > > > > > + > > > +namespace libcamera { > > > + > > > +LOG_DECLARE_CATEGORY(IPADataSerializer) > > > +{% for struct in structs_nonempty %} > > > +template<> > > > +class IPADataSerializer<{{struct|name_full(namespace_str)}}> > > > +{ > > > +public: > > > +{{- serializer.serializer(struct, base_controls, namespace_str)}} > > > +{%- if struct|has_fd %} > > > +{{serializer.deserializer_fd(struct, namespace_str)}} > > > +{%- else %} > > > +{{serializer.deserializer_no_fd(struct, namespace_str)}} > > > +{%- 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..f6836034 > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl > > > @@ -0,0 +1,205 @@ > > > +{#- 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) -%} > > > > There's one occurrence of a call to this macro with two arguments only. > > Should suffix have a default of "" ? > > That's the default default value of macro parameters in jinja, that's why > I left it blank. I thought it would be something like that. Maybe making it explicit would be more readable, or maybe that's just because I don't have experience with jinja. Up to you. > > > +{{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<uint8_t> {{param.mojom_name}}Buf; > > > +{%- if param|has_fd %} > > > + std::vector<int32_t> {{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 %} > > > + appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size()); > > > +{%- if param|has_fd %} > > > + appendPOD<uint32_t>({{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 -%} > > > > I wonder if we could optimize this by avoiding the intermediate vectors, > > but that's a candidate for a later optimization. > > I'll add it to the todo list. > > > > + > > > + > > > +{# > > > + # \brief Deserialize a single object from data buffer and fd vector > > > + # > > > + # \param pointer True deserializes the object into a dereferenced pointer > > > > s/True deserializes/If true, deserialize/ ? > > > > Same in other locations below. > > Yes. > > > > + # \param iter True treats \a buf as an iterator instead of a vector > > > + # \param data_size Variable that holds the size of the vector referenced by \a buf > > > + # > > > + # 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, iter, data_size) -%} > > > +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( > > > +{%- if not iter %} > > > + {{buf}}.begin() + {{param.mojom_name}}Start, > > > +{%- else %} > > > + {{buf}} + {{param.mojom_name}}Start, > > > +{%- endif %} > > > > Maybe > > > > {{buf}}{{- ".begin()" if not iter}} + {{param.mojom_name}}Start, > > > > ? > > Yeah that's better. > > > > +{%- if loop.last and not iter %} > > > + {{buf}}.end() > > > +{%- elif not iter %} > > > + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > > > +{%- elif iter and loop.length == 1 %} > > > + {{buf}} + {{data_size}} > > > +{%- else %} > > > + {{buf}} + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize > > > +{%- endif -%} > > > > I wonder if it would simplify the generated code if we standardized on > > Span<uint8_t>. Passing both the vector iterator and the size in separate > > arguments can more easily result in them becoming out of sync. I was > > thinking the Span<>::subspan() function could be quite helpful here, but > > it doesn't handle overflows (requesting a subspan with offset > size is > > ill-defined) :-S > > > > Not necessarily something to be addressed now, but do you think it would > > make sense ? And of course if it helps fixing some of the issues > > outlined elsewhere in this review, we could already switch to span. > > It probably would make sense... but it'll be a pain to convert :/ Let's record it in a \todo then. > > > +{{- "," 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. > > > + # \param iter True treats \a buf as an iterator instead of a vector > > > + # \param data_size Variable that holds the size of the vector referenced by \a buf > > > + # > > > + # 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 deserializing after IPC calls. > > > + #} > > > +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%} > > > +{% set ns = namespace(size_offset = 0) %} > > > +{%- if params|length > 1 %} > > > +{%- for param in params %} > > > + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > > > > const ? > > > > > +{%- if iter -%} > > > +, {{buf}} + {{data_size}} > > > +{%- endif -%} > > > +); > > > + {%- set ns.size_offset = ns.size_offset + 4 %} > > > +{%- if param|has_fd %} > > > + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} > > > > const ? > > > > > +{%- if iter -%} > > > +, {{buf}} + {{data_size}} > > > +{%- endif -%} > > > +); > > > + {%- 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}}; > > > > const ? > > > > Same in a few locations below. > > Yes. > > > > +{%- 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, iter, data_size)|indent(16, True)}} > > > + } > > > + {%- else %} > > > + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds, iter, data_size)|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..51dbeb0e > > > --- /dev/null > > > +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl > > > @@ -0,0 +1,280 @@ > > > +{#- 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}}) { > > > > > > Maybe > > if ({{dataSize}} < {{size}}) { > > > > ? > > > > > + LOG(IPADataSerializer, Error) > > > + << "Failed to deserialize {{name}}: not enough {{typename}}, expected " > > > > << "Failed to deserialize " << "{{name}}" > > << ": not enough {{typename}}, expected " > > > > This should allow the compiler to deduplicate strings. > > > > > + << ({{size}}) << ", got " << ({{dataSize}}); > > > + return ret; > > > + } > > > +{%- endmacro %} > > > + > > > + > > > +{# > > > + # \brief Serialize some field into return vector > > > + # > > > + # Generate code to serialize \a field into retData, 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, namespace, loop) %} > > > +{%- if field|is_pod or field|is_enum %} > > > + std::vector<uint8_t> {{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<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}}_); > > > + {%- endif %} > > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > > +{%- elif field|is_fd %} > > > + std::vector<uint8_t> {{field.mojom_name}}; > > > + std::vector<int32_t> {{field.mojom_name}}Fds; > > > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > > > +{%- elif field|is_controls %} > > > + if (data.{{field.mojom_name}}_.size() > 0) { > > > + std::vector<uint8_t> {{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); > > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > > + } else { > > > + appendPOD<uint32_t>(retData, 0); > > > + } > > > +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} > > > + std::vector<uint8_t> {{field.mojom_name}}; > > > + {%- if field|has_fd %} > > > + std::vector<int32_t> {{field.mojom_name}}Fds; > > > + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = > > > + {%- else %} > > > + std::tie({{field.mojom_name}}, std::ignore) = > > > + {%- endif %} > > > + {%- if field|is_array or field|is_map %} > > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); > > > + {%- elif field|is_str %} > > > + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); > > > + {%- else %} > > > + IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}_, cs); > > > + {%- endif %} > > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); > > > + {%- if field|has_fd %} > > > + appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size()); > > > + {%- endif %} > > > + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); > > > + {%- if field|has_fd %} > > > + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); > > > + {%- endif %} > > > +{%- else %} > > > + /* Unknown serialization for {{field.mojom_name}}. */ > > > +{%- endif %} > > > +{%- endmacro %} > > > > Same here, I think there's room for optimization if we could avoid the > > intermediate vectors. > > > > > + > > > + > > > +{# > > > + # \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, namespace, 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}})); > > > > Do we need the static_cast<>, doesn't > > IPADataSerializer<{{field|name}}>::deserialize() return a {{field|name}} > > ? > > Ah yes, this one doesn't need the static_cast. The next one does due to > enums though. > > > > + {%- else %} > > > + ret.{{field.mojom_name}}_ = static_cast<{{field|name}}>(IPADataSerializer<uint{{field|bit_width}}_t>::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 = readPOD<uint32_t>(m, 0, data.end()); > > > > const ? > > > > You call IPADataSerializer<{{field|name}}>::deserialize() above for POD > > types, and readPOD() here. Do we need the IPADataSerializer<> > > specializations for all the PODs, can't we call readPOD() directly ? > > No we can't get rid of the POD specializations of IPADataSerializer, > because the IPADataSerializer for vectors and maps needs them. So for > serdesing fields that are PODs we use IPADataSerializer, and for > serdesing metadata like field size, we use readPOD. > > > > + {%- 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 or field|is_str %} > > > + {%- set field_size = 4 %} > > > + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} > > > + size_t {{field.mojom_name}}Size = readPOD<uint32_t>(m, 0, data.end()); > > > > const ? > > > > > + {%- 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 = readPOD<uint32_t>(m, 4, data.end()); > > > > const ? > > > > > + {{- 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' -%} > > > > I'm afraid you've got quite a few integer overflow issues here :-S > > :S > > > > + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}} > > > + ret.{{field.mojom_name}}_ = > > > + {%- if field|is_str %} > > > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size); > > > + {%- elif field|has_fd and (field|is_array or field|is_map) %} > > > + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > > > + {%- elif field|has_fd and (not (field|is_array or field|is_map)) %} > > > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); > > > + {%- elif (not field|has_fd) and (field|is_array or field|is_map) %} > > > + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > > > + {%- else %} > > > + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); > > > + {%- endif %} > > > > Can't we advance m after reading the size fields ? That would avoid all > > the error-prone calculation, and get rid of at least some of the integer > > overflow issues. > > Oh, right. > > > > + {%- 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, namespace) %} > > > + static std::tuple<std::vector<uint8_t>, std::vector<int32_t>> > > > + serialize(const {{struct|name_full(namespace)}} &data, > > > +{%- if struct|needs_control_serializer %} > > > + ControlSerializer *cs) > > > +{%- else %} > > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > > +{%- endif %} > > > + { > > > + std::vector<uint8_t> retData; > > > +{%- if struct|has_fd %} > > > + std::vector<int32_t> retFds; > > > +{%- endif %} > > > +{%- for field in struct.fields %} > > > +{{serializer_field(field, base_controls, namespace, loop)}} > > > +{%- endfor %} > > > +{% if struct|has_fd %} > > > + return {retData, retFds}; > > > +{%- else %} > > > + return {retData, {}}; > > > +{%- 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, namespace) %} > > > + static {{struct|name_full(namespace)}} > > > + deserialize(std::vector<uint8_t> &data, > > > + std::vector<int32_t> &fds, > > > +{%- if struct|needs_control_serializer %} > > > + ControlSerializer *cs) > > > +{%- else %} > > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > > +{%- endif %} > > > + { > > > + {{struct|name_full(namespace)}} ret; > > > + std::vector<uint8_t>::iterator m = data.begin(); > > > + std::vector<int32_t>::iterator n = fds.begin(); > > > + > > > + size_t dataSize = data.size(); > > > + size_t fdsSize = fds.size(); > > > +{%- for field in struct.fields -%} > > > +{{deserializer_field(field, namespace, loop)}} > > > +{%- endfor %} > > > + return ret; > > > + } > > > + > > > + static {{struct|name_full(namespace)}} > > > + deserialize(std::vector<uint8_t>::iterator dataBegin, > > > + std::vector<uint8_t>::iterator dataEnd, > > > + std::vector<int32_t>::iterator fdsBegin, > > > + std::vector<int32_t>::iterator fdsEnd, > > > > All these should const const_iterator. There are quite a few locations > > in the deserializer where the data and fds vectors or iterators are not > > const. Could you fix that ? > > Yeah, I'll fix all of that. > > > > +{%- if struct|needs_control_serializer %} > > > + ControlSerializer *cs) > > > +{%- else %} > > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > > +{%- endif %} > > > + { > > > + std::vector<uint8_t> data(dataBegin, dataEnd); > > > > This creates a copy of the data, do we really need that ? I think you > > should turn this around, implement the deserialization in this function, > > and implement the previous function as a wrapper around this one. > > Ah, okay. > > > > + std::vector<int32_t> fds(fdsBegin, fdsEnd); > > > + return IPADataSerializer<{{struct|name_full(namespace)}}>::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, namespace) %} > > > + static {{struct|name_full(namespace)}} > > > + deserialize(std::vector<uint8_t> &data, > > > +{%- if struct|needs_control_serializer %} > > > + ControlSerializer *cs) > > > +{%- else %} > > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > > +{%- endif %} > > > + { > > > + {{struct|name_full(namespace)}} ret; > > > + std::vector<uint8_t>::iterator m = data.begin(); > > > + > > > + size_t dataSize = data.size(); > > > +{%- for field in struct.fields -%} > > > +{{deserializer_field(field, namespace, loop)}} > > > +{%- endfor %} > > > + return ret; > > > + } > > > + > > > + static {{struct|name_full(namespace)}} > > > + deserialize(std::vector<uint8_t>::iterator dataBegin, > > > + std::vector<uint8_t>::iterator dataEnd, > > > +{%- if struct|needs_control_serializer %} > > > + ControlSerializer *cs) > > > +{%- else %} > > > + [[maybe_unused]] ControlSerializer *cs = nullptr) > > > +{%- endif %} > > > + { > > > + std::vector<uint8_t> data(dataBegin, dataEnd); > > > + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data, cs); > > > + } > > > +{%- endmacro %}
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl new file mode 100644 index 00000000..a470b99e --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl @@ -0,0 +1,113 @@ +{#- SPDX-License-Identifier: LGPL-2.1-or-later -#} +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) {{year}}, Google Inc. + * + * {{module_name}}_ipa_interface.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 <libcamera/ipa/ipa_interface.h> +#include <libcamera/ipa/{{module_name}}.h> + +{% if has_map %}#include <map>{% endif %} +{% if has_array %}#include <vector>{% endif %} + +namespace libcamera { +{%- if has_namespace %} +{% for ns in namespace %} +namespace {{ns}} { +{% endfor %} +{%- endif %} + +enum class {{cmd_enum_name}} { + Exit = 0, +{%- for method in interface_main.methods %} + {{method.mojom_name|cap}} = {{loop.index}}, +{%- endfor %} +}; + +enum class {{cmd_event_enum_name}} { +{%- for method in interface_cb.methods %} + {{method.mojom_name|cap}} = {{loop.index}}, +{%- endfor %} +}; + +{% for enum in enums %} +enum {{enum.mojom_name}} { +{%- for field in enum.fields %} + {{field.mojom_name}} = {{field.numeric_value}}, +{%- 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 %} + +{#- +Any consts or #defines should be moved to the mojom file when possible. +If anything needs to be #included, then {{module_name}}.h needs to have the +#include. +#} +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} + {{- ", " if not loop.last}} +{%- endfor -%} +> {{method.mojom_name}}; +{% endfor -%} +}; + +{%- if has_namespace %} +{% for ns in namespace|reverse %} +} /* {{ns}} */ +{% endfor %} +{%- endif %} +} /* 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..9328c7ca --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl @@ -0,0 +1,238 @@ +{#- 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 <libcamera/ipa/ipa_proxy_{{module_name}}.h> + +#include <vector> + +#include <libcamera/ipa/ipa_module_info.h> +#include <libcamera/ipa/{{module_name}}.h> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> + +#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" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPAProxy) + +{%- if has_namespace %} +{% for ns in namespace %} +namespace {{ns}} { +{% endfor %} +{%- endif %} + +{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate) + : IPAProxy(ipam), running_(false), + isolate_(isolate) +{ + LOG(IPAProxy, Debug) + << "initializing {{module_name}} proxy: loading IPA from " + << ipam->path(); + + if (isolate_) { + const std::string proxyWorkerPath = resolvePath("ipa_proxy_{{module_name}}"); + if (proxyWorkerPath.empty()) { + LOG(IPAProxy, Error) + << "Failed to get proxy worker path"; + return; + } + + ipc_ = std::make_unique<IPAIPCUnixSocket>(ipam->path().c_str(), proxyWorkerPath.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(static_cast<uint32_t>({{cmd_enum_name}}::Exit), {}, {}); +} + +{% if interface_cb.methods|length > 0 %} +void {{proxy_name}}::recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &fds) +{ + if (data.size() < 8) { + LOG(IPAProxy, Error) + << "Didn't receive enough bytes to parse event"; + return; + } + + {{cmd_event_enum_name}} cmd = static_cast<{{cmd_event_enum_name}}>(( + data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); + + /* Need to skip another 4 bytes for the sequence number. */ + std::vector<uint8_t>::iterator vec = data.begin() + 8; + size_t dataSize = data.size() - 8; + switch (cmd) { +{%- for method in interface_cb.methods %} + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { + {{method.mojom_name}}IPC(vec, dataSize, fds); + break; + } +{%- endfor %} + default: + LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(cmd); + } +} +{%- 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<uint8_t> _ipcInputBuf; +{%- if method|method_input_has_fd %} + std::vector<int32_t> _ipcInputFds; +{%- endif %} +{%- endif %} +{%- if has_output %} + std::vector<uint8_t> _ipcOutputBuf; +{%- if method|method_output_has_fd %} + std::vector<int32_t> _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_enum_name + "::" + method.mojom_name|cap %} +{% if method|is_async %} + int ret = ipc_->sendAsync(static_cast<uint32_t>({{cmd}}), {{input_buf}}, {{fds_buf}}); +{%- else %} + int ret = ipc_->sendSync(static_cast<uint32_t>({{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 %} + } +{% 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<uint8_t>::iterator data, + size_t dataSize, + [[maybe_unused]] std::vector<int32_t> &fds) +{ +{%- for param in method.parameters %} + {{param|name}} {{param.mojom_name}}; +{%- endfor %} +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} + {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); +} +{% endfor %} + +{%- if has_namespace %} +{% for ns in namespace|reverse %} +} /* {{ns}} */ +{% endfor %} +{%- endif %} +} /* 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..3fb7192f --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl @@ -0,0 +1,118 @@ +{#- 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 <libcamera/ipa/ipa_interface.h> +#include <libcamera/ipa/{{module_name}}.h> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> + +#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 { +{%- if has_namespace %} +{% for ns in namespace %} +namespace {{ns}} { +{% endfor %} +{%- endif %} + +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 " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}} + {{- ", " if not loop.last}} +{%- endfor -%} +> {{method.mojom_name}}; +{% endfor %} + +private: + void recvIPC(std::vector<uint8_t> &data, std::vector<int32_t> &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<uint8_t>::iterator data, + size_t dataSize, + std::vector<int32_t> &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<IPAIPCUnixSocket> ipc_; + + ControlSerializer controlSerializer_; +}; + +{%- if has_namespace %} +{% for ns in namespace|reverse %} +} /* {{ns}} */ +{% endfor %} +{%- endif %} +} /* 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..dca4f99d --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl @@ -0,0 +1,187 @@ +{#- 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 <algorithm> +#include <iostream> +#include <sys/types.h> +#include <tuple> +#include <unistd.h> + +#include <libcamera/event_dispatcher.h> +#include <libcamera/ipa/ipa_interface.h> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h> +#include <libcamera/logging.h> + +#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) + +{%- if has_namespace %} +{% for ns in namespace -%} +using namespace {{ns}}; +{% endfor %} +{%- endif %} + +struct CallData { + IPCUnixSocket::Payload *response; + bool done; +}; + +{{interface_name}} *ipa_; +IPCUnixSocket socket_; + +ControlSerializer controlSerializer_; + +bool exit_ = false; + +void readyRead(IPCUnixSocket *socket) +{ + IPCUnixSocket::Payload _message, _response; + int _retRecv = socket->receive(&_message); + if (_retRecv) { + LOG({{proxy_name}}Worker, Error) + << "Receive message failed" << _retRecv; + return; + } + + uint32_t _cmdUint, _seq; + std::tie(_cmdUint, _seq) = IPAIPCUnixSocket::readHeader(_message); + IPAIPCUnixSocket::eraseHeader(_message); + + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_cmdUint); + + switch (_cmd) { + case {{cmd_enum_name}}::Exit: { + exit_ = true; + break; + } + +{% for method in interface_main.methods %} + case {{cmd_enum_name}}::{{method.mojom_name|cap}}: { + {{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 %} + IPAIPCUnixSocket::writeHeader(_response, _cmdUint, _seq); +{%- if method|method_return_value != "void" %} + std::vector<uint8_t> _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 %} + default: + LOG({{proxy_name}}Worker, Error) << "Unknown command " << _cmdUint; + } +} + +{% for method in interface_cb.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "", false)}} +{ + IPCUnixSocket::Payload _message; + + IPAIPCUnixSocket::writeHeader(_message, static_cast<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), 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, Error) + << "Tried to start worker with no args"; + return EXIT_FAILURE; + } + + int fd = std::stoi(argv[2]); + LOG({{proxy_name}}Worker, Info) + << "Starting worker for IPA module " << argv[1] + << " with IPC fd = " << fd; + + std::unique_ptr<IPAModule> ipam = std::make_unique<IPAModule>(argv[1]); + if (!ipam->isValid() || !ipam->load()) { + LOG({{proxy_name}}Worker, Error) + << "IPAModule " << argv[1] << " isn't valid"; + 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_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl new file mode 100644 index 00000000..675f9adf --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_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 <libcamera/ipa/{{module_name}}.h> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h> + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" + +#include <tuple> +#include <vector> + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPADataSerializer) +{% for struct in structs_nonempty %} +template<> +class IPADataSerializer<{{struct|name_full(namespace_str)}}> +{ +public: +{{- serializer.serializer(struct, base_controls, namespace_str)}} +{%- if struct|has_fd %} +{{serializer.deserializer_fd(struct, namespace_str)}} +{%- else %} +{{serializer.deserializer_no_fd(struct, namespace_str)}} +{%- 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..f6836034 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl @@ -0,0 +1,205 @@ +{#- 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<uint8_t> {{param.mojom_name}}Buf; +{%- if param|has_fd %} + std::vector<int32_t> {{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 %} + appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size()); +{%- if param|has_fd %} + appendPOD<uint32_t>({{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 + # \param iter True treats \a buf as an iterator instead of a vector + # \param data_size Variable that holds the size of the vector referenced by \a buf + # + # 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, iter, data_size) -%} +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize( +{%- if not iter %} + {{buf}}.begin() + {{param.mojom_name}}Start, +{%- else %} + {{buf}} + {{param.mojom_name}}Start, +{%- endif %} +{%- if loop.last and not iter %} + {{buf}}.end() +{%- elif not iter %} + {{buf}}.begin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize +{%- elif iter and loop.length == 1 %} + {{buf}} + {{data_size}} +{%- else %} + {{buf}} + {{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. + # \param iter True treats \a buf as an iterator instead of a vector + # \param data_size Variable that holds the size of the vector referenced by \a buf + # + # 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 deserializing after IPC calls. + #} +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%} +{% set ns = namespace(size_offset = 0) %} +{%- if params|length > 1 %} +{%- for param in params %} + [[maybe_unused]] size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} +{%- if iter -%} +, {{buf}} + {{data_size}} +{%- endif -%} +); + {%- set ns.size_offset = ns.size_offset + 4 %} +{%- if param|has_fd %} + [[maybe_unused]] size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}} +{%- if iter -%} +, {{buf}} + {{data_size}} +{%- endif -%} +); + {%- 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, iter, data_size)|indent(16, True)}} + } + {%- else %} + {{param|name + " " if declare}}{{deserialize_param(param, pointer, loop, buf, fds, iter, data_size)|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..51dbeb0e --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl @@ -0,0 +1,280 @@ +{#- 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 retData, 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, namespace, loop) %} +{%- if field|is_pod or field|is_enum %} + std::vector<uint8_t> {{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<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}}_); + {%- endif %} + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); +{%- elif field|is_fd %} + std::vector<uint8_t> {{field.mojom_name}}; + std::vector<int32_t> {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end()); +{%- elif field|is_controls %} + if (data.{{field.mojom_name}}_.size() > 0) { + std::vector<uint8_t> {{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); + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + } else { + appendPOD<uint32_t>(retData, 0); + } +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} + std::vector<uint8_t> {{field.mojom_name}}; + {%- if field|has_fd %} + std::vector<int32_t> {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + {%- else %} + std::tie({{field.mojom_name}}, std::ignore) = + {%- endif %} + {%- if field|is_array or field|is_map %} + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_, cs); + {%- elif field|is_str %} + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}_); + {%- else %} + IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}_, cs); + {%- endif %} + appendPOD<uint32_t>(retData, {{field.mojom_name}}.size()); + {%- if field|has_fd %} + appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size()); + {%- endif %} + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + {%- if field|has_fd %} + retFds.insert(retFds.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, namespace, 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<uint{{field|bit_width}}_t>::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 = readPOD<uint32_t>(m, 0, data.end()); + {%- 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 or field|is_str %} + {%- set field_size = 4 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} + size_t {{field.mojom_name}}Size = readPOD<uint32_t>(m, 0, data.end()); + {%- 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 = readPOD<uint32_t>(m, 4, data.end()); + {{- 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|is_str %} + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size); + {%- elif field|has_fd and (field|is_array or field|is_map) %} + IPADataSerializer<{{field|name}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); + {%- elif field|has_fd and (not (field|is_array or field|is_map)) %} + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m + 8, m + 8 + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs); + {%- elif (not field|has_fd) and (field|is_array or field|is_map) %} + IPADataSerializer<{{field|name}}>::deserialize(m + 4, m + 4 + {{field.mojom_name}}Size, cs); + {%- else %} + IPADataSerializer<{{field|name_full(namespace)}}>::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, namespace) %} + static std::tuple<std::vector<uint8_t>, std::vector<int32_t>> + serialize(const {{struct|name_full(namespace)}} &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector<uint8_t> retData; +{%- if struct|has_fd %} + std::vector<int32_t> retFds; +{%- endif %} +{%- for field in struct.fields %} +{{serializer_field(field, base_controls, namespace, loop)}} +{%- endfor %} +{% if struct|has_fd %} + return {retData, retFds}; +{%- else %} + return {retData, {}}; +{%- 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, namespace) %} + static {{struct|name_full(namespace)}} + deserialize(std::vector<uint8_t> &data, + std::vector<int32_t> &fds, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct|name_full(namespace)}} ret; + std::vector<uint8_t>::iterator m = data.begin(); + std::vector<int32_t>::iterator n = fds.begin(); + + size_t dataSize = data.size(); + size_t fdsSize = fds.size(); +{%- for field in struct.fields -%} +{{deserializer_field(field, namespace, loop)}} +{%- endfor %} + return ret; + } + + static {{struct|name_full(namespace)}} + deserialize(std::vector<uint8_t>::iterator dataBegin, + std::vector<uint8_t>::iterator dataEnd, + std::vector<int32_t>::iterator fdsBegin, + std::vector<int32_t>::iterator fdsEnd, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector<uint8_t> data(dataBegin, dataEnd); + std::vector<int32_t> fds(fdsBegin, fdsEnd); + return IPADataSerializer<{{struct|name_full(namespace)}}>::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, namespace) %} + static {{struct|name_full(namespace)}} + deserialize(std::vector<uint8_t> &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct|name_full(namespace)}} ret; + std::vector<uint8_t>::iterator m = data.begin(); + + size_t dataSize = data.size(); +{%- for field in struct.fields -%} +{{deserializer_field(field, namespace, loop)}} +{%- endfor %} + return ret; + } + + static {{struct|name_full(namespace)}} + deserialize(std::vector<uint8_t>::iterator dataBegin, + std::vector<uint8_t>::iterator dataEnd, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector<uint8_t> data(dataBegin, dataEnd); + return IPADataSerializer<{{struct|name_full(namespace)}}>::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..c4fbf9b3 --- /dev/null +++ b/utils/ipc/generators/mojom_libcamera_generator.py @@ -0,0 +1,488 @@ +'''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 Capitalize(name): + return name[0].upper() + name[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.*EventInterface$", 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 IsStr(element): + return element.kind.spec == 's' + +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 (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): + return 'std::string' + 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')): + if (element.spec == 's'): + return 'std::string' + 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 GetFullNameForElement(element, namespace_str): + name = GetNameForElement(element) + if namespace_str == '': + return name + return f'{namespace_str}::{name}' + +def ValidateZeroLength(l, s, cap=True): + if l is None: + return + 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.*EventInterface", x.mojom_name)] + ValidateSingleLength(intf, 'main interface') + intf = intf[0] + + # Validate presence of callback interface + cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] + ValidateSingleLength(cb, 'event interface') + cb = cb[0] + + # Validate required main interface functions + 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()') + + # Validate that all async methods don't have return values + intf_methods_async = [x for x in intf.methods if IsAsync(x)] + for method in intf_methods_async: + ValidateZeroLength(method.response_parameters, + f'{method.mojom_name} response parameters', False) + + cb_methods_async = [x for x in cb.methods if IsAsync(x)] + for method in cb_methods_async: + ValidateZeroLength(method.response_parameters, + f'{method.mojom_name} response parameters', False) + +class Generator(generator.Generator): + + @staticmethod + def GetTemplatePrefix(): + return 'libcamera_templates' + + def GetFilters(self): + libcamera_filters = { + 'all_types': GetAllTypes, + 'bit_width': BitWidth, + 'cap': Capitalize, + '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, + 'is_str': IsStr, + '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, + 'name_full': GetFullNameForElement, + 'needs_control_serializer': NeedsControlSerializer, + 'params_comma_sep': ParamsCommaSep, + 'with_default_values': WithDefaultValues, + 'with_fds': WithFds, + } + return libcamera_filters + + def _GetJinjaExports(self): + return { + 'base_controls': '%s::controls' % ModuleClassName(self.module), + 'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module), + 'cmd_event_enum_name': '_%sEventCmd' % 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.mojom_namespace != '', + '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.mojom_namespace.split('.'), + 'namespace_str': self.module.mojom_namespace.replace('.', '::') if + self.module.mojom_namespace is not None else '', + '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_ipa_interface.h.tmpl') + def _GenerateDataHeader(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_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)
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 <paul.elder@ideasonboard.com> --- Changes in v4: For the non-template files: - rename IPA{pipeline_name}CallbackInterface to IPA{pipeline_name}EventInterface - to avoid the notion of "callback" and emphasize that it's an event - add support for strings in custom structs - add validation, that async methods must not have return values - it throws exception and isn't very clear though...? - rename controls to libcamera::{pipeline_name}::controls (controls is now lowercase) - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h, and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h - same for their corresponding template files For the template files: - fix spacing (now it's all {{var}} instead of some {{ var }}) - except if it's code, so code is still {{ code }} - move inclusion of corresponding header to first in the inclusion list - fix copy&paste errors - change snake_case to camelCase in the generated code - template code still uses snake_case - change the generated command enums to an enum class, and make it capitalized (instead of allcaps) - add length checks to recvIPC (in proxy) - fix some template spacing - don't use const for PODs in function/signal parameters - add the proper length checks to readPOD/appendPOD - the helper functions for reading and writing PODs to and from serialized data - rename readUInt/appendUInt to readPOD/appendPOD - add support for strings in custom structs Changes in v3: - add support for namespaces - fix enum assignment (used to have +1 for CMD applied to all enums) - use readHeader, writeHeader, and eraseHeader as static class functions of IPAIPCUnixSocket (in the proxy worker) - add requirement that base controls *must* be defined in libcamera::{pipeline_name}::Controls 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_ipa_interface.h.tmpl | 113 ++++ .../module_ipa_proxy.cpp.tmpl | 238 +++++++++ .../module_ipa_proxy.h.tmpl | 118 +++++ .../module_ipa_proxy_worker.cpp.tmpl | 187 +++++++ .../module_ipa_serializer.h.tmpl | 44 ++ .../libcamera_templates/proxy_functions.tmpl | 205 ++++++++ .../libcamera_templates/serializer.tmpl | 280 ++++++++++ .../generators/mojom_libcamera_generator.py | 488 ++++++++++++++++++ 8 files changed, 1673 insertions(+) create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.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_ipa_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