From patchwork Thu Dec 24 08:15:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10715 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 1A23DC0F1A for ; Thu, 24 Dec 2020 08:15:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DC123615B4; Thu, 24 Dec 2020 09:15:48 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AyOwCy1s"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2053560525 for ; Thu, 24 Dec 2020 09:15:48 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8D1B0A1D; Thu, 24 Dec 2020 09:15:46 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797747; bh=Oo57KEscQYkvO1BeJ3JHiP5sQ75O5M/j/iEKTTTXWsU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AyOwCy1sTcZBRyeN1KDRogdl1VU+5QImd+KlFyPRqi3aDOlhP6R/zgKpaG3nK6slH cvg4rjCl1Zap2s2VbGvjLvZAzLNC8TjI3buFhIUujMoo1d7hFAbjERh6u1oWIcMDMi 74VcfxbSwFz78FQvOO0PqPXCOzAoClp0Luf+YMdU= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:26 +0900 Message-Id: <20201224081534.41601-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 1/9] libcamera: control_serializer: Save serialized ControlInfoMap in a cache X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The ControlSerializer saves all ControlInfoMaps that it has already (de)serialized, in order to (de)serialize ControlLists that contain the ControlInfoMaps. Leverage this to cache ControlInfoMaps, such that the ControlSerializer will not re-(de)serialize a ControlInfoMap that it has previously (de)serialized. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- New in v6 --- .../libcamera/internal/control_serializer.h | 1 + src/libcamera/control_serializer.cpp | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/include/libcamera/internal/control_serializer.h b/include/libcamera/internal/control_serializer.h index 0ab29d9a..76cb3c10 100644 --- a/include/libcamera/internal/control_serializer.h +++ b/include/libcamera/internal/control_serializer.h @@ -33,6 +33,7 @@ public: template T deserialize(ByteStreamBuffer &buffer); + bool isCached(const ControlInfoMap *infoMap); private: static size_t binarySize(const ControlValue &value); static size_t binarySize(const ControlInfo &info); diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp index 258db6df..4cf1c720 100644 --- a/src/libcamera/control_serializer.cpp +++ b/src/libcamera/control_serializer.cpp @@ -173,6 +173,11 @@ void ControlSerializer::store(const ControlInfo &info, ByteStreamBuffer &buffer) int ControlSerializer::serialize(const ControlInfoMap &infoMap, ByteStreamBuffer &buffer) { + if (isCached(&infoMap)) { + LOG(Serializer, Info) << "Serializing ControlInfoMap from cache"; + return 0; + } + /* Compute entries and data required sizes. */ size_t entriesSize = infoMap.size() * sizeof(struct ipa_control_info_entry); @@ -347,6 +352,12 @@ ControlInfoMap ControlSerializer::deserialize(ByteStreamBuffer & return {}; } + auto iter = infoMaps_.find(hdr->handle); + if (iter != infoMaps_.end()) { + LOG(Serializer, Info) << "Deserializing ControlInfoMap from cache"; + return iter->second; + } + if (hdr->version != IPA_CONTROLS_FORMAT_VERSION) { LOG(Serializer, Error) << "Unsupported controls format version " @@ -485,4 +496,23 @@ ControlList ControlSerializer::deserialize(ByteStreamBuffer &buffer return ctrls; } +/** + * \brief Check if some ControlInfoMap is cached + * \param[in] infoMap The ControlInfoMap to check + * + * The ControlSerializer caches all ControlInfoMaps that it has (de)serialized. + * This function checks if \a infoMap is in the cache. + * + * \return True if \a infoMap is in the cache or if \a infoMap is + * controls::controls, false otherwise + */ +bool ControlSerializer::isCached(const ControlInfoMap *infoMap) +{ + if (!infoMap) + return true; + + auto iter = infoMapHandles_.find(infoMap); + return iter != infoMapHandles_.end(); +} + } /* namespace libcamera */ From patchwork Thu Dec 24 08:15:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10716 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 6B82DC0F1A for ; Thu, 24 Dec 2020 08:15:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3857F619E2; Thu, 24 Dec 2020 09:15:52 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Elg2AYXw"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A34B60525 for ; Thu, 24 Dec 2020 09:15:51 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 988F0A1D; Thu, 24 Dec 2020 09:15:48 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797750; bh=PUq0AvpZ54w/vhGq6L8a+Sohy1CrSqzYBmZUpSgM2hI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Elg2AYXw46PWSaX6URVv0YOBtxaB2eh/da5c3hNgXQQHM1OwF1/PtVldmZunvAJds 5fg9DMVbMn8YeBpdU9li2E72IyJoybaiOLNEHDE16UZlgX0SvhmDLFaEi3Fmk17+rH 6Bip5fSYvRS/8KcbtGv0ZNtmnowwLdJuHNiXNyYg= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:27 +0900 Message-Id: <20201224081534.41601-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 2/9] utils: ipc: add templates for code generation for IPC mechanism X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add templates to mojo to generate code for the IPC mechanism. These templates generate: - module header - module serializer - IPA proxy cpp, header, and worker Given an input data definition mojom file for a pipeline. Signed-off-by: Paul Elder Acked-by: Jacopo Mondi Acked-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- Changes in v6: - add templates for core_ipa_interface.h and core_ipa_serializer.h - for libcamera types defined in mojom - rename everything to {{module_name}}_ipa_{interface,proxy,proxy_worker}.{c,h} - remove #include event - refactor Method{Input,Output}HasFd with a helper MethodParamsHaveFd - add Get{Main,Event}Interface to fix the interface_{main,event} jinja exports - add copyright - require that event interfaces have at least one event - expand copyright for templates - use new sendSync/sendAsync API (with IPCMessage) - rename a bunch of things 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 --- .../core_ipa_interface.h.tmpl | 37 ++ .../core_ipa_serializer.h.tmpl | 47 ++ .../definition_functions.tmpl | 53 ++ .../libcamera_templates/meson.build | 14 + .../module_ipa_interface.h.tmpl | 87 +++ .../module_ipa_proxy.cpp.tmpl | 232 ++++++++ .../module_ipa_proxy.h.tmpl | 126 +++++ .../module_ipa_proxy_worker.cpp.tmpl | 224 ++++++++ .../module_ipa_serializer.h.tmpl | 47 ++ .../libcamera_templates/proxy_functions.tmpl | 192 +++++++ .../libcamera_templates/serializer.tmpl | 313 +++++++++++ .../generators/mojom_libcamera_generator.py | 511 ++++++++++++++++++ 12 files changed, 1883 insertions(+) create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/definition_functions.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/meson.build 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/core_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl new file mode 100644 index 00000000..f11d56fb --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl @@ -0,0 +1,37 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "definition_functions.tmpl" as funcs -%} +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * core_ipa_interface.h - libcamera core definitions for Image Processing Algorithms + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__ +#define __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__ + +{% if has_map %}#include {% endif %} +{% if has_array %}#include {% endif %} + +namespace libcamera { + +{% for const in consts %} +const {{const.kind|name}} {{const.mojom_name}} = {{const.value}}; +{% endfor %} + +{% for enum in enums %} +{{funcs.define_enum(enum)}} +{% endfor %} + +{%- for struct in structs_gen_header %} +{{funcs.define_struct(struct)}} +{% endfor %} + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__ */ diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl new file mode 100644 index 00000000..37a784f1 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl @@ -0,0 +1,47 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "serializer.tmpl" as serializer -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * core_ipa_serializer.h - Data serializer for core libcamera definitions for IPA + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__ +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__ + +#include +#include + +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPADataSerializer) +{% for struct in structs_gen_serializer %} +template<> +class IPADataSerializer<{{struct|name}}> +{ +public: +{{- serializer.serializer(struct, "")}} +{%- if struct|has_fd %} +{{serializer.deserializer_fd(struct, "")}} +{%- else %} +{{serializer.deserializer_no_fd(struct, "")}} +{{serializer.deserializer_fd_simple(struct, "")}} +{%- endif %} +}; +{% endfor %} + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__ */ diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl new file mode 100644 index 00000000..cdd75f89 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl @@ -0,0 +1,53 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} + +{# + # \brief Generate enum definition + # + # \param enum Enum object whose definition is to be generated + #} +{%- macro define_enum(enum) -%} +enum {{enum.mojom_name}} { +{%- for field in enum.fields %} + {{field.mojom_name}} = {{field.numeric_value}}, +{%- endfor %} +}; +{%- endmacro -%} + +{# + # \brief Generate struct definition + # + # \param struct Struct object whose definition is to be generated + #} +{%- macro define_struct(struct) -%} +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}}( +{%- for field in struct.fields -%} +{{"const " if not field|is_pod}}{{field|name}} {{"&" if not field|is_pod}}_{{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 %} +}; +{%- endmacro -%} + + diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/ipc/generators/libcamera_templates/meson.build new file mode 100644 index 00000000..70664eab --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 + +mojom_template_files = files([ + 'core_ipa_interface.h.tmpl', + 'core_ipa_serializer.h.tmpl', + 'definition_functions.tmpl', + 'module_ipa_interface.h.tmpl', + 'module_ipa_proxy.cpp.tmpl', + 'module_ipa_proxy.h.tmpl', + 'module_ipa_proxy_worker.cpp.tmpl', + 'module_ipa_serializer.h.tmpl', + 'proxy_functions.tmpl', + 'serializer.tmpl', +]) 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..afbcb1b1 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl @@ -0,0 +1,87 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "definition_functions.tmpl" as funcs -%} +/* 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. + */ + +#ifndef __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__ +#define __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__ + +#include +#include + +{% if has_map %}#include {% endif %} +{% if has_array %}#include {% endif %} + +namespace libcamera { +{%- if has_namespace %} +{% for ns in namespace %} +namespace {{ns}} { +{% endfor %} +{%- endif %} + +{% for const in consts %} +const {{const.kind|name}} {{const.mojom_name}} = {{const.value}}; +{% endfor %} + +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_event.methods %} + {{method.mojom_name|cap}} = {{loop.index}}, +{%- endfor %} +}; + +{% for enum in enums %} +{{funcs.define_enum(enum)}} +{% endfor %} + +{%- for struct in structs_nonempty %} +{{funcs.define_struct(struct)}} +{% endfor %} + +{#- +Any consts or #defines should be moved to the mojom file. +#} +class {{interface_name}} : public IPAInterface +{ +public: +{% 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_event.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 %} +} /* namespace {{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..a181cc84 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl @@ -0,0 +1,232 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * {{module_name}}_ipa_proxy.cpp - Image Processing Algorithm proxy for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include + +#include +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/ipa_proxy.h" +#include "libcamera/internal/ipc_pipe.h" +#include "libcamera/internal/ipc_pipe_unixsocket.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("{{module_name}}_ipa_proxy"); + if (proxyWorkerPath.empty()) { + LOG(IPAProxy, Error) + << "Failed to get proxy worker path"; + return; + } + + ipc_ = std::make_unique(ipam->path().c_str(), + proxyWorkerPath.c_str()); + if (!ipc_->isConnected()) { + LOG(IPAProxy, Error) << "Failed to create IPCPipe"; + return; + } + + ipc_->recv.connect(this, &{{proxy_name}}::recvMessage); + + 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}}>(static_cast<{{interface_name}} *>(ipai)); + proxy_.setIPA(ipa_.get()); + +{% for method in interface_event.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({{cmd_enum_name}}::Exit), {}); +} + +{% if interface_event.methods|length > 0 %} +void {{proxy_name}}::recvMessage(uint32_t cmd, const IPCMessage &data) +{ + size_t dataSize = data.cdata().size(); + {{cmd_event_enum_name}} _cmd = static_cast<{{cmd_event_enum_name}}>(cmd); + + switch (_cmd) { +{%- for method in interface_event.methods %} + case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: { + {{method.mojom_name}}IPC(data.cdata().cbegin(), dataSize, data.cfds()); + break; + } +{%- endfor %} + default: + LOG(IPAProxy, Error) << "Unknown command " << static_cast(_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 == "stop" %} + {{proxy_funcs.stop_thread_body()}} +{%- elif method.mojom_name == "start" %} + running_ = true; + thread_.start(); + + {{ "return " if method|method_return_value != "void" -}} + proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking + {{- ", " if method|method_param_names}} + {%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} + {%- endfor -%} +); +{%- elif not method|is_async %} + {{ "return " if method|method_return_value != "void" -}} + 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 %} + IPCMessage _ipcInputBuf; +{%- endif %} +{%- if has_output %} + IPCMessage _ipcOutputBuf; +{%- endif %} + +{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf.data()', '_ipcInputBuf.fds()')}} + +{%- set input_buf = "_ipcInputBuf" if has_input else "{}" %} +{%- set cmd = cmd_enum_name + "::" + method.mojom_name|cap %} +{% if method|is_async %} + int _ret = ipc_->sendAsync(static_cast({{cmd}}), {{input_buf}}); +{%- else %} + int _ret = ipc_->sendSync(static_cast({{cmd}}), {{input_buf}} +{{- ", &_ipcOutputBuf" if has_output -}} +); +{%- 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.data(), 0); +{% elif method|method_param_outputs|length > 0 %} +{{proxy_funcs.deserialize_call(method|method_param_outputs, '_ipcOutputBuf.data()', '_ipcOutputBuf.fds()')}} +{% endif -%} +} + +{% endfor %} + +{% for method in interface_event.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::const_iterator data, + size_t dataSize, + [[maybe_unused]] const std::vector &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 %} +} /* namespace {{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..b0f04bf6 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl @@ -0,0 +1,126 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * {{module_name}}_ipa_proxy.h - Image Processing Algorithm proxy for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__ +#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__ + +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipc_pipe.h" +#include "libcamera/internal/ipc_pipe_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_event.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 recvMessage(uint32_t cmd, const IPCMessage &data); + +{% 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_event.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "Thread", false)|indent(8, true)}}; + void {{method.mojom_name}}IPC( + std::vector::const_iterator data, + size_t dataSize, + const std::vector &fds); +{% endfor %} + + /* Helper class to invoke async functions in another thread. */ + class ThreadProxy : public Object + { + public: + void setIPA({{interface_name}} *ipa) + { + ipa_ = ipa; + } + + 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}}); + } +{%- elif method.mojom_name == "start" %} + {{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}} + { +{%- if method|method_return_value != "void" %} + return 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 -}} + {{- method|method_param_outputs|params_comma_sep}}); +{%- endif %} + } +{%- endif %} +{%- endfor %} + + private: + {{interface_name}} *ipa_; + }; + + bool running_; + Thread thread_; + ThreadProxy proxy_; + std::unique_ptr<{{interface_name}}> ipa_; + + const bool isolate_; + + std::unique_ptr ipc_; + + ControlSerializer controlSerializer_; +}; + +{%- if has_namespace %} +{% for ns in namespace|reverse %} +} /* namespace {{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..cb3df4eb --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl @@ -0,0 +1,224 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "proxy_functions.tmpl" as proxy_funcs -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * {{module_name}}_ipa_proxy_worker.cpp - Image Processing Algorithm proxy worker for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +{#- \todo Split proxy worker into IPC worker and proxy worker. #} + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/event_dispatcher.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipc_pipe.h" +#include "libcamera/internal/ipc_pipe_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_worker_name}}) + +{%- if has_namespace %} +{% for ns in namespace -%} +using namespace {{ns}}; +{% endfor %} +{%- endif %} + +class {{proxy_worker_name}} +{ +public: + {{proxy_worker_name}}() + : ipa_(nullptr), exit_(false) {} + + ~{{proxy_worker_name}}() {} + + void readyRead(IPCUnixSocket *socket) + { + IPCUnixSocket::Payload _message; + int _retRecv = socket->receive(&_message); + if (_retRecv) { + LOG({{proxy_worker_name}}, Error) + << "Receive message failed" << _retRecv; + return; + } + + IPCMessage _ipcMessage(_message); + + {{cmd_enum_name}} _cmd = static_cast<{{cmd_enum_name}}>(_ipcMessage.header().cmd); + + 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, '_ipcMessage.data()', '_ipcMessage.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 %} + IPCMessage::Header header = { _ipcMessage.header().cmd, _ipcMessage.header().cookie }; + IPCMessage _response(header); +{%- if method|method_return_value != "void" %} + std::vector _callRetBuf; + std::tie(_callRetBuf, std::ignore) = + IPADataSerializer<{{method|method_return_value}}>::serialize(_callRet); + _response.data().insert(_response.data().end(), _callRetBuf.cbegin(), _callRetBuf.cend()); +{%- else %} + {{proxy_funcs.serialize_call(method|method_param_outputs, "_response.data()", "_response.fds()")|indent(16, true)}} +{%- endif %} + int _ret = socket_.send(_response.payload()); + if (_ret < 0) { + LOG({{proxy_worker_name}}, Error) + << "Reply to {{method.mojom_name}}() failed" << _ret; + } + LOG({{proxy_worker_name}}, Debug) << "Done replying to {{method.mojom_name}}()"; +{%- endif %} + break; + } +{% endfor %} + default: + LOG({{proxy_worker_name}}, Error) << "Unknown command " << _ipcMessage.header().cmd; + } + } + + int init(std::unique_ptr &ipam, int socketfd) + { + if (socket_.bind(socketfd) < 0) { + LOG({{proxy_worker_name}}, Error) + << "IPC socket binding failed"; + return EXIT_FAILURE; + } + socket_.readyRead.connect(this, &{{proxy_worker_name}}::readyRead); + + ipa_ = dynamic_cast<{{interface_name}} *>(ipam->createInterface()); + if (!ipa_) { + LOG({{proxy_worker_name}}, Error) + << "Failed to create IPA interface instance"; + return EXIT_FAILURE; + } +{% for method in interface_event.methods %} + ipa_->{{method.mojom_name}}.connect(this, &{{proxy_worker_name}}::{{method.mojom_name}}); +{%- endfor %} + return 0; + } + + void run() + { + EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + while (!exit_) + dispatcher->processEvents(); + } + + void cleanup() + { + delete ipa_; + socket_.close(); + } + +private: + +{% for method in interface_event.methods %} +{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(8, true)}} + { + IPCMessage::Header header = { + static_cast({{cmd_event_enum_name}}::{{method.mojom_name|cap}}), + 0 + }; + IPCMessage _message(header); + + {{proxy_funcs.serialize_call(method|method_param_inputs, "_message.data()", "_message.fds()")}} + + socket_.send(_message.payload()); + + LOG({{proxy_worker_name}}, Debug) << "{{method.mojom_name}} done"; + } +{% endfor %} + + {{interface_name}} *ipa_; + IPCUnixSocket socket_; + + ControlSerializer controlSerializer_; + + bool exit_; +}; + +int main(int argc, char **argv) +{ +{#- \todo Handle enabling debugging more dynamically. #} + /* Uncomment this for debugging. */ + std::string logPath = "/tmp/libcamera.worker." + + std::to_string(getpid()) + ".log"; + logSetFile(logPath.c_str()); + + if (argc < 3) { + LOG({{proxy_worker_name}}, Error) + << "Tried to start worker with no args: " + << "expected "; + return EXIT_FAILURE; + } + + int fd = std::stoi(argv[2]); + LOG({{proxy_worker_name}}, Info) + << "Starting worker for IPA module " << argv[1] + << " with IPC fd = " << fd; + + std::unique_ptr ipam = std::make_unique(argv[1]); + if (!ipam->isValid() || !ipam->load()) { + LOG({{proxy_worker_name}}, Error) + << "IPAModule " << argv[1] << " isn't valid"; + return EXIT_FAILURE; + } + + {{proxy_worker_name}} proxyWorker; + int ret = proxyWorker.init(ipam, fd); + if (ret < 0) { + LOG({{proxy_worker_name}}, Error) + << "Failed to initialize proxy worker"; + return ret; + } + + LOG({{proxy_worker_name}}, Debug) << "Proxy worker successfully started"; + + proxyWorker.run(); + + proxyWorker.cleanup(); + + 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..ad522964 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl @@ -0,0 +1,47 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{%- import "serializer.tmpl" as serializer -%} + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * {{module_name}}_ipa_serializer.h - Image Processing Algorithm data serializer for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__ +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__ + +#include +#include + +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_data_serializer.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPADataSerializer) +{% for struct in structs_nonempty %} +template<> +class IPADataSerializer<{{struct|name_full(namespace_str)}}> +{ +public: +{{- serializer.serializer(struct, 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..3f2143b1 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl @@ -0,0 +1,192 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{# + # \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 If true, generate class name with function + # \param override If true, 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 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) %} +{%- for param in params %} + std::vector {{param.mojom_name}}Buf; +{%- if param|has_fd %} + std::vector {{param.mojom_name}}Fds; + std::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) = +{%- else %} + std::tie({{param.mojom_name}}Buf, std::ignore) = +{%- endif %} + IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}} +{{- ", &controlSerializer_" if param|needs_control_serializer -}} +); +{%- endfor %} + +{%- if params|length > 1 %} +{%- for param in params %} + appendPOD({{buf}}, {{param.mojom_name}}Buf.size()); +{%- if param|has_fd %} + appendPOD({{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 If true, deserialize the object into a dereferenced pointer + # \param iter If true, treat \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( + {{buf}}{{- ".cbegin()" if not iter}} + {{param.mojom_name}}Start, +{%- if loop.last and not iter %} + {{buf}}.cend() +{%- elif not iter %} + {{buf}}.cbegin() + {{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}}.cbegin() + {{param.mojom_name}}FdStart, +{%- if loop.last %} + {{fds}}.cend() +{%- else %} + {{fds}}.cbegin() + {{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 If true, deserialize objects into pointers, and adds a null check. + # \param declare If true, declare the objects in addition to deserialization. + # \param iter if true, treat \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. + # + # \todo Avoid intermediate vectors + #} +{%- 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]] const size_t {{param.mojom_name}}BufSize = readPOD({{buf}}, {{ns.size_offset}} +{%- if iter -%} +, {{buf}} + {{data_size}} +{%- endif -%} +); + {%- set ns.size_offset = ns.size_offset + 4 %} +{%- if param|has_fd %} + [[maybe_unused]] const size_t {{param.mojom_name}}FdsSize = readPOD({{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 %} + const size_t {{param.mojom_name}}Start = {{ns.size_offset}}; +{%- else %} + const 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 %} + const size_t {{param.mojom_name}}FdStart = 0; +{%- elif not loop.last %} + const 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..af4800bf --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl @@ -0,0 +1,313 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +{# 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 ({{dataSize}} < {{size}}) { + 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). + # This code is meant to be used by the IPADataSerializer specialization. + # + # \todo Avoid intermediate vectors + #} +{%- macro serializer_field(field, namespace, loop) %} +{%- if field|is_pod or field|is_enum %} + std::vector {{field.mojom_name}}; + std::tie({{field.mojom_name}}, std::ignore) = + {%- if field|is_pod %} + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}); + {%- elif field|is_enum %} + IPADataSerializer::serialize(data.{{field.mojom_name}}); + {%- endif %} + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); +{%- elif field|is_fd %} + std::vector {{field.mojom_name}}; + std::vector {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}); + 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 {{field.mojom_name}}; + std::tie({{field.mojom_name}}, std::ignore) = + IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}, cs); + appendPOD(retData, {{field.mojom_name}}.size()); + retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end()); + } else { + appendPOD(retData, 0); + } +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %} + std::vector {{field.mojom_name}}; + {%- if field|has_fd %} + std::vector {{field.mojom_name}}Fds; + std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) = + {%- else %} + std::tie({{field.mojom_name}}, std::ignore) = + {%- endif %} + {%- 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(retData, {{field.mojom_name}}.size()); + {%- if field|has_fd %} + appendPOD(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}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field_size}}); + {%- else %} + ret.{{field.mojom_name}} = static_cast<{{field|name_full(namespace)}}>(IPADataSerializer::deserialize(m, m + {{field_size}})); + {%- endif %} + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- endif %} +{% elif field|is_fd %} + {%- set field_size = 1 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}} + ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + 1, n, n + 1, cs); + {%- if not loop.last %} + m += {{field_size}}; + dataSize -= {{field_size}}; + n += ret.{{field.mojom_name}}.isValid() ? 1 : 0; + fdsSize -= ret.{{field.mojom_name}}.isValid() ? 1 : 0; + {%- endif %} +{% elif field|is_controls %} + {%- set field_size = 4 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}} + const size_t {{field.mojom_name}}Size = readPOD(m, 0, dataEnd); + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- set field_size = 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, m + {{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')}} + const size_t {{field.mojom_name}}Size = readPOD(m, 0, dataEnd); + m += {{field_size}}; + dataSize -= {{field_size}}; + {%- if field|has_fd %} + {%- set field_size = 4 %} + {{- check_data_size(field_size, 'dataSize', field.mojom_name + 'FdsSize', 'data')}} + const size_t {{field.mojom_name}}FdsSize = readPOD(m, 0, dataEnd); + m += {{field_size}}; + dataSize -= {{field_size}}; + {{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds')}} + {%- endif %} + {%- set field_size = 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, m + {{field.mojom_name}}Size); + {%- elif field|has_fd and (field|is_array or field|is_map) %} + IPADataSerializer<{{field|name}}>::deserialize(m, m + {{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, m + {{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, m + {{field.mojom_name}}Size, cs); + {%- else %} + IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m, m + {{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. + #} +{%- macro serializer(struct, namespace) %} + static std::tuple, std::vector> + serialize(const {{struct|name_full(namespace)}} &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + std::vector retData; +{%- if struct|has_fd %} + std::vector retFds; +{%- endif %} +{%- for field in struct.fields %} +{{serializer_field(field, 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. + # fd parameters + #} +{%- macro deserializer_fd(struct, namespace) %} + static {{struct|name_full(namespace)}} + deserialize(std::vector &data, + std::vector &fds, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), fds.cbegin(), fds.cend(), cs); + } + + static {{struct|name_full(namespace)}} + deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + std::vector::const_iterator fdsEnd, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct|name_full(namespace)}} ret; + std::vector::const_iterator m = dataBegin; + std::vector::const_iterator n = fdsBegin; + + size_t dataSize = std::distance(dataBegin, dataEnd); + [[maybe_unused]] size_t fdsSize = std::distance(fdsBegin, fdsEnd); +{%- for field in struct.fields -%} +{{deserializer_field(field, namespace, loop)}} +{%- endfor %} + return ret; + } +{%- endmacro %} + +{# + # \brief Deserialize a struct that has fds, using non-fd + # + # Generate code for IPADataSerializer specialization, for deserializing + # \a struct, in the case that \a struct has no file descriptors but requires + # deserializers with file descriptors. + #} +{%- macro deserializer_fd_simple(struct, namespace) %} + static {{struct|name_full(namespace)}} + deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs); + } + + static {{struct|name_full(namespace)}} + deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(dataBegin, dataEnd, 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 &data, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs); + } + + static {{struct|name_full(namespace)}} + deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, +{%- if struct|needs_control_serializer %} + ControlSerializer *cs) +{%- else %} + [[maybe_unused]] ControlSerializer *cs = nullptr) +{%- endif %} + { + {{struct|name_full(namespace)}} ret; + std::vector::const_iterator m = dataBegin; + + size_t dataSize = std::distance(dataBegin, dataEnd); +{%- for field in struct.fields -%} +{{deserializer_field(field, namespace, loop)}} +{%- endfor %} + return ret; + } +{%- 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..5c4ad4fe --- /dev/null +++ b/utils/ipc/generators/mojom_libcamera_generator.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2020, Google Inc. +# +# Author: Paul Elder +# +# mojom_libcamera_generator.py - Generates libcamera files from a mojom.Module. + +import argparse +import datetime +import os +import re + +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): + return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1), + module.interfaces[0].mojom_name) + +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 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): + if hasattr(element, 'attributes'): + return element.attributes or {} + return {} + attrs = [(x.attributes) for x in element.fields] + ret = {} + for d in attrs: + ret.update(d or {}) + if hasattr(element, 'attributes'): + ret.update(element.attributes or {}) + 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 WithDefaultValues(element): + return [x for x in element if HasDefaultValue(x)] + +def WithFds(element): + return [x for x in element if HasFd(x)] + +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 MethodParamsHaveFd(parameters): + return len([x for x in parameters if HasFd(x)]) > 0 + +def MethodInputHasFd(method): + return MethodParamsHaveFd(method.parameters) + +def MethodOutputHasFd(method): + if (MethodReturnValue(method) != 'void' or + method.response_parameters is None): + return False + return MethodParamsHaveFd(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): + # Events 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 '' + +# Get the type name for a given element +def GetNameForElement(element): + # structs + if (mojom.IsEnumKind(element) or + mojom.IsInterfaceKind(element) or + mojom.IsStructKind(element)): + return element.mojom_name + # vectors + if (mojom.IsArrayKind(element)): + elem_name = GetNameForElement(element.kind) + return f'std::vector<{elem_name}>' + # maps + if (mojom.IsMapKind(element)): + key_name = GetNameForElement(element.key_kind) + value_name = GetNameForElement(element.value_kind) + return f'std::map<{key_name}, {value_name}>' + # struct fields and function parameters + if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)): + # maps and vectors + if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)): + return GetNameForElement(element.kind) + # strings + if (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'): + return 'std::string' + # PODs + if element.kind in _kind_to_cpp_type: + return _kind_to_cpp_type[element.kind] + # structs and enums + return element.kind.mojom_name + # PODs that are members of vectors/maps + if (hasattr(element, '__hash__') and element in _kind_to_cpp_type): + return _kind_to_cpp_type[element] + if (hasattr(element, 'spec')): + # strings that are members of vectors/maps + if (element.spec == 's'): + return 'std::string' + # structs that aren't defined in mojom that are members of vectors/maps + if (element.spec[0] == 'x'): + return element.spec.replace('x:', '').replace('.', '::') + 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 GetMainInterface(interfaces): + 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') + return None if len(intf) == 0 else intf[0] + +def GetEventInterface(interfaces): + event = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)] + ValidateSingleLength(event, 'event interface') + return None if len(event) == 0 else event[0] + +def ValidateNamespace(namespace): + if namespace == '': + raise Exception('Must have a namespace') + + if not re.match('^ipa\.[0-9A-Za-z_]+', namespace): + raise Exception('Namespace must be of the form "ipa.{pipeline_name}"') + +def ValidateInterfaces(interfaces): + # Validate presence of main interface + intf = GetMainInterface(interfaces) + if intf is None: + raise Exception('Must have main IPA interface') + + # Validate presence of event interface + event = GetEventInterface(interfaces) + if intf is None: + raise Exception('Must have event IPA interface') + + # 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') + + # No need to validate start() as it is customizable + + # Validate parameters to stop() + ValidateZeroLength(f_stop.parameters, 'input parameter to stop()') + ValidateZeroLength(f_stop.parameters, 'output parameter from stop()') + + # Validate that event interface has at least one event + if len(event.methods) < 1: + raise Exception('Event interface must have at least one event') + + # 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) + + event_methods_async = [x for x in event.methods if IsAsync(x)] + for method in event_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 { + 'cmd_enum_name': '_%sCmd' % self.module_name, + 'cmd_event_enum_name': '_%sEventCmd' % self.module_name, + 'consts': self.module.constants, + '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 != '', + 'interface_event': GetEventInterface(self.module.interfaces), + 'interface_main': GetMainInterface(self.module.interfaces), + 'interface_name': 'IPA%sInterface' % self.module_name, + 'module_name': ModuleName(self.module.path), + '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' % self.module_name, + 'proxy_worker_name': 'IPAProxy%sWorker' % self.module_name, + 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0], + } + + def _GetJinjaExportsForCore(self): + return { + 'consts': self.module.constants, + '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, + 'structs_gen_header': [x for x in self.module.structs if x.attributes is not None and 'genHeader' in x.attributes], + 'structs_gen_serializer': [x for x in self.module.structs if x.attributes is not None and 'genSerdes' in x.attributes], + } + + @UseJinja('core_ipa_interface.h.tmpl') + def _GenerateCoreHeader(self): + return self._GetJinjaExportsForCore() + + @UseJinja('core_ipa_serializer.h.tmpl') + def _GenerateCoreSerializer(self): + return self._GetJinjaExportsForCore() + + @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_core_header', action='store_true') + parser.add_argument('--libcamera_generate_core_serializer', action='store_true') + 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) + + if not args.libcamera_generate_core_header and \ + not args.libcamera_generate_core_serializer: + ValidateNamespace(self.module.mojom_namespace) + ValidateInterfaces(self.module.interfaces) + self.module_name = ModuleClassName(self.module) + + fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path)) + + gen_funcs = [ + [args.libcamera_generate_core_header, self._GenerateCoreHeader], + [args.libcamera_generate_core_serializer, self._GenerateCoreSerializer], + [args.libcamera_generate_header, self._GenerateDataHeader], + [args.libcamera_generate_serializer, self._GenerateSerializer], + [args.libcamera_generate_proxy_cpp, self._GenerateProxyCpp], + [args.libcamera_generate_proxy_h, self._GenerateProxyHeader], + [args.libcamera_generate_proxy_worker, self._GenerateProxyWorker], + ] + + for pair in gen_funcs: + if pair[0]: + self.Write(pair[1](), args.libcamera_output_path) From patchwork Thu Dec 24 08:15:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10717 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 02374C0F1A for ; Thu, 24 Dec 2020 08:15:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C0E07615B0; Thu, 24 Dec 2020 09:15:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fCdBU71t"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F2E4160529 for ; Thu, 24 Dec 2020 09:15:53 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AA42BA1D; Thu, 24 Dec 2020 09:15:51 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797753; bh=GJCDgoHy0PDVZSU4MlgvnfsrgkPKkrnBfQQn5nGZS1g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fCdBU71tj6h54kG1vp6KluyY/lTGJyY/oZGfk9Cw9qIlcfKRTSSrn83XcOfPJXACY AMG+dVoYgiwW9LsRXKHJrbZbTr6M589Z1rBROYSCS4my/YZyJtXaGRRDXs8eB/Greb 7av3si10wAAsbCiVqI5XO5/AT7YxGLz+4df0CZOw= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:28 +0900 Message-Id: <20201224081534.41601-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 3/9] utils: ipc: add generator script X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" We want to avoid changing our copy of mojo to make updates easier. Some parameters in the mojo generator script needs to be changed though; add a wrapper script that sets these parameters. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- No change in v6 No change in v5 No change in v4 Changes in v3: - add SPDX and copyright and file description block - add todo for sys.pycache_prefix for python3.8+ Changes in v2: - add descriptions to python setup - disable pycache --- utils/ipc/generate.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 utils/ipc/generate.py diff --git a/utils/ipc/generate.py b/utils/ipc/generate.py new file mode 100755 index 00000000..8771e0a6 --- /dev/null +++ b/utils/ipc/generate.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2020, Google Inc. +# +# Author: Paul Elder +# +# generate.py - Run mojo code generator for generating libcamera IPC files + +import os +import sys + +# TODO set sys.pycache_prefix for >= python3.8 +sys.dont_write_bytecode = True + +import mojo.public.tools.bindings.mojom_bindings_generator as generator + +def _GetModulePath(path, output_dir): + return os.path.join(output_dir, path.relative_path()) + +# Override the mojo code generator's generator list to only contain our +# libcamera generator +generator._BUILTIN_GENERATORS = {'libcamera': 'mojom_libcamera_generator'} + +# Override the mojo code generator's _GetModulePath method to not add +# the '-module' suffix when searching for mojo modules, so that we can +# pass the path to the mojom module without having to trim the '-module' suffix +generator._GetModulePath = _GetModulePath + +generator.main() From patchwork Thu Dec 24 08:15:29 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10718 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 57F6AC0F1A for ; Thu, 24 Dec 2020 08:15:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1F78D615D5; Thu, 24 Dec 2020 09:15:57 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BHbYe7DJ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 835B760525 for ; Thu, 24 Dec 2020 09:15:56 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 595D5FE0; Thu, 24 Dec 2020 09:15:54 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797756; bh=zaWrGM46NtMmalpKVsHJZ0hhs3AVLA4vwFrXSrfGSQ0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BHbYe7DJeA21Lfewltk+9SBxJIdH1BTyiamc7gd8P1ZdL9YEaVAW3T7BznpCdd7RZ kTVEEHRQk1ZBJDDEL4ewB3QdG1vmS+KR+NjYjTupco3fr4Y3cAuIXbL6WOcY5croWG zcVNnjDqwK15IzuYeG3QiNT/ZWqZdh845L0BWYps= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:29 +0900 Message-Id: <20201224081534.41601-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 4/9] utils: ipc: add parser script X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" On some systems, python2 might still be the default python. Enforce python3 by wrapping the mojo parser script in a python3 script. This also has the benefit of not modifying mojo. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- No change in v6 No change in v5 No change in v4 New in v3 --- utils/ipc/parser.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 utils/ipc/parser.py diff --git a/utils/ipc/parser.py b/utils/ipc/parser.py new file mode 100755 index 00000000..f46820fa --- /dev/null +++ b/utils/ipc/parser.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2020, Google Inc. +# +# Author: Paul Elder +# +# parser.py - Run mojo parser with python3 + +import os +import sys + +# TODO set sys.pycache_prefix for >= python3.8 +sys.dont_write_bytecode = True + +# Make sure that mojom_parser.py can import mojom +sys.path.append(f'{os.path.dirname(__file__)}/mojo/public/tools/mojom') + +import mojo.public.tools.mojom.mojom_parser as parser + +parser.Run(sys.argv[1:]) From patchwork Thu Dec 24 08:15:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10719 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id A924FC0F1A for ; Thu, 24 Dec 2020 08:16:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 747FF615B0; Thu, 24 Dec 2020 09:16:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P55iQBkO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1720360525 for ; Thu, 24 Dec 2020 09:15:59 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 05736DFC; Thu, 24 Dec 2020 09:15:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797758; bh=SGhCt6lhVJQlF6rkxMW9crjTueAgodJPX8P4rmvxd0M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P55iQBkOxdRqN3hzOYCqK+C4B9fRKZGFKblwqXEkoOr2cb/+u8R1f6DTwJfbiqAEP Rct1qgD6h7qhaUqJLCeTO25haZrv3MqRC7hcUR/gIyDg97FrUeIwDKpBATK+wpyihH qNS1/RUiS9Q2u+Grf1UKlT/dKEXJMQZ9QWtgJ0O4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:30 +0900 Message-Id: <20201224081534.41601-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 5/9] Documentation: skip generating documentation for generated code X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Specify in doxygen to not generate code for the generated headers and proxy source code files. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- No change in v6 No change in v5 No change in v4 No change in v3 New in v2 --- Documentation/Doxyfile.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in index 4bbacc46..b18b8e9c 100644 --- a/Documentation/Doxyfile.in +++ b/Documentation/Doxyfile.in @@ -842,7 +842,9 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/span.h \ @TOP_SRCDIR@/src/libcamera/pipeline/ \ @TOP_SRCDIR@/src/libcamera/proxy/ \ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ - @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h + @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ + @TOP_BUILDDIR@/include/libcamera/ipa/ \ + @TOP_BUILDDIR@/src/libcamera/proxy/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded From patchwork Thu Dec 24 08:15:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10720 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 0EC94C0F1A for ; Thu, 24 Dec 2020 08:16:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D1764615AF; Thu, 24 Dec 2020 09:16:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aiqS9MU+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6C01760525 for ; Thu, 24 Dec 2020 09:16:01 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8E2D4A1D; Thu, 24 Dec 2020 09:15:59 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797761; bh=1vWrzxN36u0lfl+udZ6GzdTqw48MfTnfF2BPn6KIFwQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=aiqS9MU+a03BrRzFPEAMigaX0vpk3OeNSlv/GbCQhsPJRDSkj7FkBwT77hZwCX3DG KcafM9WCx8uJUK9OsUFiUnpsp5raM7GTHP4xZHstajlIrUE1Wiqo1Rh9IHAHVX2Crz +NvrL2RySaLye5pV9IhjEw9hc6qT75dFbAtvjTVM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:31 +0900 Message-Id: <20201224081534.41601-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 6/9] libcamera: Add IPADataSerializer X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an IPADataSerializer which implments de/serialization of built-in (PODs, vector, map, string) and libcamera data structures. This is intended to be used by the proxy and the proxy worker in the IPC layer. Signed-off-by: Paul Elder Reviewed-by: Jacopo Mondi Acked-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- Changes in v6: - remove (de)serializers for IPASettings, CameraSensorInfo, IPAStream, and IPABuffer as they are now defined in core.mojom and generated - ControlList (de)serializer no longer needs a ControlInfoMap - ControlList (de)serializer now checks the ControlSerializer if the ControlList's ControlInfoMap is cached - add some todos for optimization and hardening Changes in v5: - fix style of: {{}, {}} -> { {}, {} } - add documentation on serialization formats (not in doxygen, though) - compress readPOD - use memcpy - use ASSERT - remove ifdef DOXYGEN guard from base IPADataSerializer - remove integer overflow risk when calculating vector offsets - use const iterators and const references - remove const ControlList and const ControlInfoMap specializations Changes in v4: - rename readUInt/appendUInt to readPOD/appendPOD - put them in anonymous namespace - and add length check - fatal if failure, because it means not enough data to deserialize, which is technically a segfault - use the new readPOD/appendPOD with length protections - expand on their docs correspondingly - change snake_case to camelCase - add optimizations to the hand-written de/serializers - reserve vector length where trivially possible - remove unnecessary IPADataSerializer:: explicit calls (if they're calling a specialization from the same specialization) Changes in v3: - reimplement append/readUInt with memcpy (intead of bit shifting) - change DECLARE_INTEGRAL_SERIALIZER with DECLARE_POD_SERIALIZER - use this for int64_t, bool, float, and double - fix comment style Changes in v2: - added serializers for all integer types, bool, and string - use string serializer for IPASettings serializer - add documentation - add serializer for const ControlList --- .../libcamera/internal/ipa_data_serializer.h | 701 ++++++++++++++++++ src/libcamera/ipa_data_serializer.cpp | 185 +++++ src/libcamera/meson.build | 1 + 3 files changed, 887 insertions(+) create mode 100644 include/libcamera/internal/ipa_data_serializer.h create mode 100644 src/libcamera/ipa_data_serializer.cpp diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h new file mode 100644 index 00000000..5468a0b7 --- /dev/null +++ b/include/libcamera/internal/ipa_data_serializer.h @@ -0,0 +1,701 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipa_data_serializer.h - Image Processing Algorithm data serializer + */ +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__ +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libcamera/internal/byte_stream_buffer.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/log.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPADataSerializer) + +namespace { + +template +void appendPOD(std::vector &vec, + typename std::enable_if, T>::type val) +{ + size_t byteWidth = sizeof(val); + vec.resize(vec.size() + byteWidth); + memcpy(&*(vec.end() - byteWidth), &val, byteWidth); +} + +template +typename std::enable_if, T>::type +readPOD(std::vector::const_iterator it, size_t pos, + std::vector::const_iterator end) +{ + ASSERT(pos + it < end); + + T ret = 0; + memcpy(&ret, &(*(it + pos)), sizeof(ret)); + + return ret; +} + +template +typename std::enable_if, T>::type +readPOD(std::vector &vec, size_t pos) +{ + return readPOD(vec.cbegin(), pos, vec.end()); +} + +} /* namespace */ + +template +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const T &data, ControlSerializer *cs); + + static T deserialize(const std::vector &data, + ControlSerializer *cs); + static T deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + ControlSerializer *cs); + + static T deserialize(const std::vector &data, + const std::vector &fds, + ControlSerializer *cs); + static T deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + std::vector::const_iterator fdsEnd, + ControlSerializer *cs); +}; + +#ifndef __DOXYGEN__ + +/* + * Serialization format for vector of type V: + * + * 4 bytes - uint32_t Length of vector, in number of elements + * + * For every element in the vector: + * + * 4 bytes - uint32_t Size of element, in bytes + * 4 bytes - uint32_t Size of fds for the element, in number of fds + * X bytes - Serialized element + */ +template +class IPADataSerializer> +{ +public: + static std::tuple, std::vector> + serialize(const std::vector &data, ControlSerializer *cs = nullptr) + { + std::vector dataVec; + std::vector fdsVec; + + /* Serialize the length. */ + uint32_t vecLen = data.size(); + appendPOD(dataVec, vecLen); + + /* Serialize the members. */ + for (auto const &it : data) { + std::vector dvec; + std::vector fvec; + + std::tie(dvec, fvec) = + IPADataSerializer::serialize(it, cs); + + appendPOD(dataVec, dvec.size()); + appendPOD(dataVec, fvec.size()); + + dataVec.insert(dataVec.end(), dvec.begin(), dvec.end()); + fdsVec.insert(fdsVec.end(), fvec.begin(), fvec.end()); + } + + return { dataVec, fdsVec }; + } + + static std::vector deserialize(std::vector &data, ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static std::vector deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + ControlSerializer *cs = nullptr) + { + std::vector fds; + return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.end(), cs); + } + + static std::vector deserialize(std::vector &data, std::vector &fds, + ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); + } + + static std::vector deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + ControlSerializer *cs = nullptr) + { + uint32_t vecLen = readPOD(dataBegin, 0, dataEnd); + std::vector ret(vecLen); + + std::vector::const_iterator dataIter = dataBegin + 4; + std::vector::const_iterator fdIter = fdsBegin; + for (uint32_t i = 0; i < vecLen; i++) { + uint32_t sizeofData = readPOD(dataIter, 0, dataEnd); + uint32_t sizeofFds = readPOD(dataIter, 4, dataEnd); + dataIter += 8; + + ret[i] = IPADataSerializer::deserialize(dataIter, + dataIter + sizeofData, + fdIter, + fdIter + sizeofFds, + cs); + + dataIter += sizeofData; + fdIter += sizeofFds; + } + + return ret; + } +}; + +/* + * Serialization format for map of key type K and value type V: + * + * 4 bytes - uint32_t Length of map, in number of pairs + * + * For every pair in the map: + * + * 4 bytes - uint32_t Size of key, in bytes + * 4 bytes - uint32_t Size of fds for the key, in number of fds + * X bytes - Serialized key + * 4 bytes - uint32_t Size of value, in bytes + * 4 bytes - uint32_t Size of fds for the value, in number of fds + * X bytes - Serialized value + */ +template +class IPADataSerializer> +{ +public: + static std::tuple, std::vector> + serialize(const std::map &data, ControlSerializer *cs = nullptr) + { + std::vector dataVec; + std::vector fdsVec; + + /* Serialize the length. */ + uint32_t mapLen = data.size(); + appendPOD(dataVec, mapLen); + + /* Serialize the members. */ + for (auto const &it : data) { + std::vector dvec; + std::vector fvec; + + std::tie(dvec, fvec) = + IPADataSerializer::serialize(it.first, cs); + + appendPOD(dataVec, dvec.size()); + appendPOD(dataVec, fvec.size()); + + dataVec.insert(dataVec.end(), dvec.begin(), dvec.end()); + fdsVec.insert(fdsVec.end(), fvec.begin(), fvec.end()); + + std::tie(dvec, fvec) = + IPADataSerializer::serialize(it.second, cs); + + appendPOD(dataVec, dvec.size()); + appendPOD(dataVec, fvec.size()); + + dataVec.insert(dataVec.end(), dvec.begin(), dvec.end()); + fdsVec.insert(fdsVec.end(), fvec.begin(), fvec.end()); + } + + return { dataVec, fdsVec }; + } + + static std::map deserialize(std::vector &data, ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static std::map deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + ControlSerializer *cs = nullptr) + { + std::vector fds; + return deserialize(dataBegin, dataEnd, fds.cbegin(), fds.end(), cs); + } + + static std::map deserialize(std::vector &data, std::vector &fds, + ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); + } + + static std::map deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + ControlSerializer *cs = nullptr) + { + std::map ret; + + uint32_t mapLen = readPOD(dataBegin, 0, dataEnd); + + std::vector::const_iterator dataIter = dataBegin + 4; + std::vector::const_iterator fdIter = fdsBegin; + for (uint32_t i = 0; i < mapLen; i++) { + uint32_t sizeofData = readPOD(dataIter, 0, dataEnd); + uint32_t sizeofFds = readPOD(dataIter, 4, dataEnd); + dataIter += 8; + + K key = IPADataSerializer::deserialize(dataIter, + dataIter + sizeofData, + fdIter, + fdIter + sizeofFds, + cs); + + dataIter += sizeofData; + fdIter += sizeofFds; + sizeofData = readPOD(dataIter, 0, dataEnd); + sizeofFds = readPOD(dataIter, 4, dataEnd); + dataIter += 8; + + const V value = IPADataSerializer::deserialize(dataIter, + dataIter + sizeofData, + fdIter, + fdIter + sizeofFds, + cs); + ret.insert({key, value}); + + dataIter += sizeofData; + fdIter += sizeofFds; + } + + return ret; + } +}; + +#define DECLARE_POD_SERIALIZER(type) \ +template<> \ +class IPADataSerializer \ +{ \ +public: \ + static std::tuple, std::vector> \ + serialize(const type data, \ + [[maybe_unused]] ControlSerializer *cs = nullptr) \ + { \ + std::vector dataVec; \ + dataVec.reserve(sizeof(type)); \ + appendPOD(dataVec, data); \ + \ + return { dataVec, {} }; \ + } \ + \ + static type deserialize(std::vector &data, \ + [[maybe_unused]] ControlSerializer *cs = nullptr)\ + { \ + return deserialize(data.cbegin(), data.end()); \ + } \ + \ + static type deserialize(std::vector::const_iterator dataBegin,\ + [[maybe_unused]] std::vector::const_iterator dataEnd,\ + [[maybe_unused]] ControlSerializer *cs = nullptr)\ + { \ + return readPOD(dataBegin, 0, dataEnd); \ + } \ + \ + static type deserialize(std::vector &data, \ + [[maybe_unused]] std::vector &fds,\ + [[maybe_unused]] ControlSerializer *cs = nullptr)\ + { \ + return deserialize(data.cbegin(), data.end()); \ + } \ + \ + static type deserialize(std::vector::const_iterator dataBegin,\ + std::vector::const_iterator dataEnd,\ + [[maybe_unused]] std::vector::const_iterator fdsBegin,\ + [[maybe_unused]] std::vector::const_iterator fdsEnd,\ + [[maybe_unused]] ControlSerializer *cs = nullptr)\ + { \ + return deserialize(dataBegin, dataEnd); \ + } \ +}; + +DECLARE_POD_SERIALIZER(bool) +DECLARE_POD_SERIALIZER(uint8_t) +DECLARE_POD_SERIALIZER(uint16_t) +DECLARE_POD_SERIALIZER(uint32_t) +DECLARE_POD_SERIALIZER(uint64_t) +DECLARE_POD_SERIALIZER(int8_t) +DECLARE_POD_SERIALIZER(int16_t) +DECLARE_POD_SERIALIZER(int32_t) +DECLARE_POD_SERIALIZER(int64_t) +DECLARE_POD_SERIALIZER(float) +DECLARE_POD_SERIALIZER(double) + +/* + * Strings are serialized simply by converting by {string.cbegin(), string.end()}. + * The size of the string is recorded by the container (struct, vector, map, or + * function parameter serdes). + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const std::string &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { { data.cbegin(), data.end() }, {} }; + } + + static std::string deserialize(std::vector &data, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { data.cbegin(), data.cend() }; + } + + static std::string deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { dataBegin, dataEnd }; + } + + static std::string deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { data.cbegin(), data.cend() }; + } + + static std::string deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { dataBegin, dataEnd }; + } +}; + +/* + * FileDescriptors are serialized into a single byte that tells if the + * FileDescriptor is valid or not. If it is valid, then for serialization + * the fd will be written to the fd vector, or for deserialization the + * fd vector const_iterator will be valid. + * + * This validity is necessary so that we don't send -1 fd over sendmsg(). It + * also allows us to simply send the entire fd vector into the deserializer + * and it will be recursively consumed as necessary. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const FileDescriptor &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + std::vector dataVec = { data.isValid() }; + std::vector fdVec; + if (data.isValid()) + fdVec.push_back(data.fd()); + + return { dataVec, fdVec }; + } + + static FileDescriptor deserialize(std::vector &data, std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end()); + } + + static FileDescriptor deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + std::vector::const_iterator fdsEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + ASSERT(std::distance(dataBegin, dataEnd) >= 1); + + bool valid = !!(*dataBegin); + + ASSERT(!(valid && std::distance(fdsBegin, fdsEnd) < 1)); + + return valid ? FileDescriptor(*fdsBegin) : FileDescriptor(); + } +}; + +/* + * ControlList is serialized as: + * + * 4 bytes - uint32_t Size of serialized ControlInfoMap, in bytes + * 4 bytes - uint32_t Size of serialized ControlList, in bytes + * X bytes - Serialized ControlInfoMap (using ControlSerializer) + * X bytes - Serialized ControlList (using ControlSerializer) + * + * If data.infoMap() is nullptr, then the default controls::controls will + * be used. The serialized ControlInfoMap will have zero length. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const ControlList &data, ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for serialization of ControlList"; + + size_t size; + std::vector infoData; + int ret; + + if (data.infoMap() && !cs->isCached(data.infoMap())) { + size = cs->binarySize(*data.infoMap()); + infoData.resize(size); + ByteStreamBuffer buffer(infoData.data(), infoData.size()); + ret = cs->serialize(*data.infoMap(), buffer); + + if (ret < 0 || buffer.overflow()) { + LOG(IPADataSerializer, Error) << "Failed to serialize ControlList's ControlInfoMap"; + return { {}, {} }; + } + } + + size = cs->binarySize(data); + std::vector listData(size); + ByteStreamBuffer buffer(listData.data(), listData.size()); + ret = cs->serialize(data, buffer); + + if (ret < 0 || buffer.overflow()) { + LOG(IPADataSerializer, Error) << "Failed to serialize ControlList"; + return { {}, {} }; + } + + std::vector dataVec; + dataVec.reserve(8 + infoData.size() + listData.size()); + appendPOD(dataVec, infoData.size()); + appendPOD(dataVec, listData.size()); + dataVec.insert(dataVec.end(), infoData.begin(), infoData.end()); + dataVec.insert(dataVec.end(), listData.begin(), listData.end()); + + return { dataVec, {} }; + } + + static ControlList deserialize(std::vector &data, ControlSerializer *cs) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static ControlList deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for deserialization of ControlList"; + + if (!std::distance(dataBegin, dataEnd)) + return {}; + + uint32_t infoDataSize = readPOD(dataBegin, 0, dataEnd); + uint32_t listDataSize = readPOD(dataBegin, 4, dataEnd); + + std::vector::const_iterator it = dataBegin + 8; + + std::vector infoData(it, it + infoDataSize); + std::vector listData(it + infoDataSize, it + infoDataSize + listDataSize); + + if (infoDataSize > 0) { + ByteStreamBuffer buffer(const_cast(infoData.data()), infoData.size()); + ControlInfoMap map = cs->deserialize(buffer); + /* It's fine if map is empty. */ + if (buffer.overflow()) { + LOG(IPADataSerializer, Error) + << "Failed to deserialize ControlLists's ControlInfoMap: buffer overflow"; + return ControlList(); + } + } + + ByteStreamBuffer buffer(const_cast(listData.data()), listData.size()); + ControlList list = cs->deserialize(buffer); + if (buffer.overflow()) + LOG(IPADataSerializer, Error) << "Failed to deserialize ControlList: buffer overflow"; + if (list.empty()) + LOG(IPADataSerializer, Error) << "Failed to deserialize ControlList: empty list"; + + return list; + } + + static ControlList deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + ControlSerializer *cs) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static ControlList deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + ControlSerializer *cs) + { + return deserialize(dataBegin, dataEnd, cs); + } +}; + +/* + * const ControlInfoMap is serialized as: + * + * 4 bytes - uint32_t Size of serialized ControlInfoMap, in bytes + * X bytes - Serialized ControlInfoMap (using ControlSerializer) + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const ControlInfoMap &map, ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for serialization of ControlInfoMap"; + + size_t size = cs->binarySize(map); + std::vector infoData(size); + ByteStreamBuffer buffer(infoData.data(), infoData.size()); + int ret = cs->serialize(map, buffer); + + if (ret < 0 || buffer.overflow()) { + LOG(IPADataSerializer, Error) << "Failed to serialize ControlInfoMap"; + return { {}, {} }; + } + + std::vector dataVec; + appendPOD(dataVec, infoData.size()); + dataVec.insert(dataVec.end(), infoData.begin(), infoData.end()); + + return { dataVec, {} }; + } + + static ControlInfoMap deserialize(std::vector &data, + ControlSerializer *cs) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static ControlInfoMap deserialize(std::vector::const_iterator dataBegin, + [[maybe_unused]] std::vector::const_iterator dataEnd, + ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for deserialization of ControlInfoMap"; + + uint32_t infoDataSize = readPOD(dataBegin, 0, dataEnd); + + std::vector::const_iterator it = dataBegin + 4; + + std::vector infoData(it, it + infoDataSize); + + ByteStreamBuffer buffer(const_cast(infoData.data()), infoData.size()); + ControlInfoMap map = cs->deserialize(buffer); + + return map; + } + + static ControlInfoMap deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + ControlSerializer *cs) + { + return deserialize(data.cbegin(), data.end(), cs); + } + + static ControlInfoMap deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + ControlSerializer *cs) + { + return deserialize(dataBegin, dataEnd, cs); + } +}; + +/* + * FrameBuffer::Plane is serialized as: + * + * 1 byte - FileDescriptor + * 4 bytes - uint32_t Length + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const FrameBuffer::Plane &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + std::vector dataVec; + std::vector fdsVec; + + std::vector fdBuf; + std::vector fdFds; + std::tie(fdBuf, fdFds) = + IPADataSerializer::serialize(data.fd); + dataVec.insert(dataVec.end(), fdBuf.begin(), fdBuf.end()); + fdsVec.insert(fdsVec.end(), fdFds.begin(), fdFds.end()); + + appendPOD(dataVec, data.length); + + return { dataVec, fdsVec }; + } + + static FrameBuffer::Plane deserialize(std::vector &data, + std::vector &fds, + ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); + } + + static FrameBuffer::Plane deserialize(std::vector::const_iterator dataBegin, + [[maybe_unused]] std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + [[maybe_unused]] std::vector::const_iterator fdsEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + FrameBuffer::Plane ret; + + ret.fd = IPADataSerializer::deserialize(dataBegin, dataBegin + 1, + fdsBegin, fdsBegin + 1); + ret.length = readPOD(dataBegin, 1, dataEnd); + + return ret; + } +}; + +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__ */ diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp new file mode 100644 index 00000000..34287d6a --- /dev/null +++ b/src/libcamera/ipa_data_serializer.cpp @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipa_data_serializer.cpp - Image Processing Algorithm data serializer + */ + +#include "libcamera/internal/ipa_data_serializer.h" + +#include "libcamera/internal/log.h" + +/** + * \file ipa_data_serializer.h + * \brief IPA Data Serializer + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPADataSerializer) + +/** + * \class IPADataSerializer + * \brief IPA Data Serializer + * + * Static template class that provides functions for serializing and + * deserializing IPA data. + * + * \todo Switch to Span instead of byte and fd vector + * + * \todo Harden the vector and map deserializer + * + * \todo For FileDescriptors, instead of storing a validity flag, store an + * index into the fd array. This will allow us to use views instead of copying. + */ + +namespace { + +/** + * \fn template void appendPOD(std::vector &vec, T val) + * \brief Append POD to end of byte vector, in little-endian order + * \tparam T Type of POD to append + * \param[in] vec Byte vector to append to + * \param[in] val Value to append + * + * This function is meant to be used by the IPA data serializer, and the + * generated IPA proxies. + */ + +/** + * \fn template T readPOD(std::vector::iterator it, size_t pos, + * std::vector::iterator end) + * \brief Read POD from byte vector, in little-endian order + * \tparam T Type of POD to read + * \param[in] it Iterator of byte vector to read from + * \param[in] pos Index in byte vector to read from + * \param[in] end Iterator marking end of byte vector + * + * This function is meant to be used by the IPA data serializer, and the + * generated IPA proxies. + * + * If the \a pos plus the byte-width of the desired POD is past \a end, it is + * a fata error will occur, as it means there is insufficient data for + * deserialization, which should never happen. + * + * \return The POD read from \a it at index \a pos + */ + +/** + * \fn template T readPOD(std::vector &vec, size_t pos) + * \brief Read POD from byte vector, in little-endian order + * \tparam T Type of POD to read + * \param[in] vec Byte vector to read from + * \param[in] pos Index in vec to start reading from + * + * This function is meant to be used by the IPA data serializer, and the + * generated IPA proxies. + * + * If the \a pos plus the byte-width of the desired POD is past the end of + * \a vec, a fatal error will occur, as it means there is insufficient data + * for deserialization, which should never happen. + * + * \return The POD read from \a vec at index \a pos + */ + +} /* namespace */ + +/** + * \fn template IPADataSerializer::serialize( + * T data, + * ControlSerializer *cs = nullptr) + * \brief Serialize an object into byte vector and fd vector + * \tparam T Type of object to serialize + * \param[in] data Object to serialize + * \param[in] cs ControlSerializer + * + * \a cs is only necessary if the object type \a T or its members contain + * ControlList or ControlInfoMap. + * + * \return Tuple of byte vector and fd vector, that is the serialized form + * of \a data + */ + +/** + * \fn template IPADataSerializer::deserialize( + * const std::vector &data, + * ControlSerializer *cs = nullptr) + * \brief Deserialize byte vector into an object + * \tparam T Type of object to deserialize to + * \param[in] data Byte vector to deserialize from + * \param[in] cs ControlSerializer + * + * This version of deserialize() can be used if the object type \a T and its + * members don't have any FileDescriptor. + * + * \a cs is only necessary if the object type \a T or its members contain + * ControlList or ControlInfoMap. + * + * \return The deserialized object + */ + +/** + * \fn template IPADataSerializer::deserialize( + * std::vector::const_iterator dataBegin, + * std::vector::const_iterator dataEnd, + * ControlSerializer *cs = nullptr) + * \brief Deserialize byte vector into an object + * \tparam T Type of object to deserialize to + * \param[in] dataBegin Begin iterator of byte vector to deserialize from + * \param[in] dataEnd End iterator of byte vector to deserialize from + * \param[in] cs ControlSerializer + * + * This version of deserialize() can be used if the object type \a T and its + * members don't have any FileDescriptor. + * + * \a cs is only necessary if the object type \a T or its members contain + * ControlList or ControlInfoMap. + * + * \return The deserialized object + */ + +/** + * \fn template IPADataSerializer::deserialize( + * const std::vector &data, + * const std::vector &fds, + * ControlSerializer *cs = nullptr) + * \brief Deserialize byte vector and fd vector into an object + * \tparam T Type of object to deserialize to + * \param[in] data Byte vector to deserialize from + * \param[in] fds Fd vector to deserialize from + * \param[in] cs ControlSerializer + * + * This version of deserialize() (or the iterator version) must be used if + * the object type \a T or its members contain FileDescriptor. + * + * \a cs is only necessary if the object type \a T or its members contain + * ControlList or ControlInfoMap. + * + * \return The deserialized object + */ + +/** + * \fn template IPADataSerializer::deserialize( + * std::vector::const_iterator dataBegin, + * std::vector::const_iterator dataEnd, + * std::vector::const_iterator fdsBegin, + * std::vector::const_iterator fdsEnd, + * ControlSerializer *cs = nullptr) + * \brief Deserialize byte vector and fd vector into an object + * \tparam T Type of object to deserialize to + * \param[in] dataBegin Begin iterator of byte vector to deserialize from + * \param[in] dataEnd End iterator of byte vector to deserialize from + * \param[in] fdsBegin Begin iterator of fd vector to deserialize from + * \param[in] fdsEnd End iterator of fd vector to deserialize from + * \param[in] cs ControlSerializer + * + * This version of deserialize() (or the vector version) must be used if + * the object type \a T or its members contain FileDescriptor. + * + * \a cs is only necessary if the object type \a T or its members contain + * ControlList or ControlInfoMap. + * + * \return The deserialized object + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 387d5d88..1291f1b3 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -25,6 +25,7 @@ libcamera_sources = files([ 'geometry.cpp', 'ipa_context_wrapper.cpp', 'ipa_controls.cpp', + 'ipa_data_serializer.cpp', 'ipa_interface.cpp', 'ipa_manager.cpp', 'ipa_module.cpp', From patchwork Thu Dec 24 08:15:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10721 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 96337C0F1A for ; Thu, 24 Dec 2020 08:16:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 640C9615B4; Thu, 24 Dec 2020 09:16:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RzwtDZFM"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0203960525 for ; Thu, 24 Dec 2020 09:16:04 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CC8AEA1D; Thu, 24 Dec 2020 09:16:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797763; bh=IrBVC9KT07HMJfwe2q//Wk3hCe9zaH1YBd2OoAkM0y0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RzwtDZFMSsKXXvNebhSEIUzrzZmG4oZupAGZHUmD/CKN1BRWL3yLcVKRKwdMocFzo W55y/bVKtZUv3bAQcCIGq3KnhnvV+P1ZcQi+neUkFo297s24Itp48Je3gS3QgHybDU ZXKLy9VHHOA7d71NWxScXtp58GBGEbYJs997QGCc= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:32 +0900 Message-Id: <20201224081534.41601-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 7/9] libcamera: Add IPCPipe X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Create a virtual IPCPipe class that models an IPC/RPC system. IPA proxies and proxy workers will call into the IPCPipe, rather than implementing the IPC themselves. Signed-off-by: Paul Elder Reviewed-by: Jacopo Mondi Reviewed-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- No change in v6 Changes in v5: - fix style - rename ipa_ipc.[(cpp)h] to ipc_pipe.[(cpp),h] - rename IPAIPC to IPCPipe - add IPCMessage to use in the IPCPipe Change in v4: - change snake_case to camelCase - remove ipaModulePath and ipaProxyWorkerPath from parameters in the IPAIPC base contructor - include signal.h Changes in v3: - expand documentation a bit - move [[maybe_unused]] to after the parameters in the IPAIPC constructor, to appease gcc <= 9.1 Changes in v2: - add documentation --- .../libcamera/internal/ipa_data_serializer.h | 2 +- include/libcamera/internal/ipc_pipe.h | 70 ++++++ src/libcamera/ipc_pipe.cpp | 211 ++++++++++++++++++ src/libcamera/meson.build | 1 + 4 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 include/libcamera/internal/ipc_pipe.h create mode 100644 src/libcamera/ipc_pipe.cpp diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h index 5468a0b7..bde5eaf7 100644 --- a/include/libcamera/internal/ipa_data_serializer.h +++ b/include/libcamera/internal/ipa_data_serializer.h @@ -286,7 +286,7 @@ public: fdIter, fdIter + sizeofFds, cs); - ret.insert({key, value}); + ret.insert({ key, value }); dataIter += sizeofData; fdIter += sizeofFds; diff --git a/include/libcamera/internal/ipc_pipe.h b/include/libcamera/internal/ipc_pipe.h new file mode 100644 index 00000000..0232c3b5 --- /dev/null +++ b/include/libcamera/internal/ipc_pipe.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipc_pipe.h - Image Processing Algorithm IPC module for IPA proxies + */ +#ifndef __LIBCAMERA_INTERNAL_IPA_IPC_H__ +#define __LIBCAMERA_INTERNAL_IPA_IPC_H__ + +#include + +#include "libcamera/internal/ipc_unixsocket.h" + +#include + +namespace libcamera { + +class IPCMessage +{ +public: + struct Header { + uint32_t cmd; + uint32_t cookie; + }; + + IPCMessage(); + IPCMessage(const Header &header); + IPCMessage(const IPCUnixSocket::Payload &payload); + + const IPCUnixSocket::Payload payload(const Header *header = nullptr) const; + + Header &header() { return header_; } + std::vector &data() { return data_; } + std::vector &fds() { return fds_; } + + const Header &cheader() const { return header_; } + const std::vector &cdata() const { return data_; } + const std::vector &cfds() const { return fds_; } + +private: + Header header_; + + std::vector data_; + std::vector fds_; +}; + +class IPCPipe +{ +public: + IPCPipe(); + virtual ~IPCPipe(); + + bool isConnected() const { return connected_; } + + virtual int sendSync(uint32_t cmd, + const IPCMessage &in, + IPCMessage *out) = 0; + + virtual int sendAsync(uint32_t cmd, + const IPCMessage &data) = 0; + + Signal recv; + +protected: + bool connected_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_IPC_H__ */ diff --git a/src/libcamera/ipc_pipe.cpp b/src/libcamera/ipc_pipe.cpp new file mode 100644 index 00000000..eb439724 --- /dev/null +++ b/src/libcamera/ipc_pipe.cpp @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipc_pipe.cpp - Image Processing Algorithm IPC module for IPA proxies + */ + +#include "libcamera/internal/ipc_pipe.h" + +#include "libcamera/internal/log.h" + +/** + * \file ipc_pipe.h + * \brief IPC mechanism for IPA isolation + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPCPipe) + +/** + * \struct IPCMessage::Header + * \brief Container for an IPCMessage header + * + * Holds a cmd code for the IPC message, and a cookie. + */ + +/** + * \var IPCMessage::Header::cmd + * \brief Type of IPCMessage + * + * Typically used to carry a command code for an RPC. + */ + +/** + * \var IPCMessage::Header::cookie + * \brief Cookie to idetify the message and a corresponding reply. + * + * Populated and used by IPCPipe implementations for matching calls with + * replies. + */ + +/** + * \class IPCMessage + * \brief IPC message to be passed through IPC message pipe + */ + +/** + * \brief Construct an IPCMessage instance + */ +IPCMessage::IPCMessage() + : header_(Header{ 0, 0 }) +{ +} + +/** + * \brief Construct an IPCMessage instance with a given header + * \param[in] header The header that the constructed IPCMessage will contain + */ +IPCMessage::IPCMessage(const Header &header) + : header_(header) +{ +} + +/** + * \brief Construct an IPCMessage instance from an IPC payload + * \param[in] payload The IPCUnixSocket payload to construct from + * + * This essentially converts an IPCUnixSocket payload into an IPCMessage. + * The header is extracted from the payload into the IPCMessage's header field. + */ +IPCMessage::IPCMessage(const IPCUnixSocket::Payload &payload) +{ + memcpy(&header_, &*payload.data.begin(), sizeof(header_)); + data_ = std::vector(payload.data.begin() + sizeof(header_), + payload.data.end()); + fds_ = std::vector(payload.fds.begin(), payload.fds.end()); +} + +/** + * \brief Create an IPCUnixSocket payload from the IPCMessage + * \param[in] header Header to use when constructing the payload, if applicable + * + * This essentially converts the IPCMessage into an IPCUnixSocket payload. If + * \a header is not provided (ie. nullptr), then the header field that is + * internal to the IPCMessage will be used. + */ +const IPCUnixSocket::Payload IPCMessage::payload(const Header *header) const +{ + IPCUnixSocket::Payload message; + + message.data.resize(sizeof(Header) + data_.size()); + message.fds.reserve(fds_.size()); + + memcpy(&*message.data.begin(), header ? header : &header_, sizeof(Header)); + + /* \todo Make this work without copy */ + memcpy(&*(message.data.begin() + sizeof(Header)), data_.data(), data_.size()); + message.fds = const_cast &>(fds_); + + return message; +} + +/** + * \fn IPCMessage::header() + * \brief Returns a reference to the header + */ + +/** + * \fn IPCMessage::data() + * \brief Returns a reference to the byte vector containing data + */ + +/** + * \fn IPCMessage::fds() + * \brief Returns a reference to the vector containing file descriptors + */ + +/** + * \fn IPCMessage::cheader() + * \brief Returns a const reference to the header + */ + +/** + * \fn IPCMessage::cdata() + * \brief Returns a const reference to the byte vector containing data + */ + +/** + * \fn IPCMessage::cfds() + * \brief Returns a const reference to the vector containing file descriptors + */ + +/** + * \class IPCPipe + * \brief IPC message pipe for IPA isolation + * + * Virtual class to model an IPC message pipe for use by IPA proxies for IPA + * isolation. sendSync() and sendAsync() must be implemented, and the recvMessage + * signal must be emitted whenever new data is available. + */ + +/** + * \brief Construct an IPCPipe instance + */ +IPCPipe::IPCPipe() + : connected_(false) +{ +} + +IPCPipe::~IPCPipe() +{ +} + +/** + * \fn IPCPipe::isConnected() + * \brief Check if the IPCPipe instance is connected + * + * An IPCPipe instance is connected if IPC is successfully set up. + * + * \return True if the IPCPipe is connected, false otherwise + */ + +/** + * \fn IPCPipe::sendSync() + * \brief Send a message over IPC synchronously + * \param[in] cmd Command ID + * \param[in] in Data to send, as an IPCMessage + * \param[in] out IPCMessage instance in which to receive data, if applicable + * + * This function will not return until a response is received. The event loop + * will still continue to execute, however. + * + * \return Zero on success, negative error code otherwise + */ + +/** + * \fn IPCPipe::sendAsync() + * \brief Send a message over IPC asynchronously + * \param[in] cmd Command ID + * \param[in] data Data to send, as an IPCMessage + * + * This function will return immediately after sending the message. + * + * \return Zero on success, negative error code otherwise + */ + +/** + * \var IPCPipe::recv + * \brief Signal to be emitted when data is received over IPC + * + * When data is received over IPC, this signal shall be emitted. Users must + * connect to this to receive new data. + * + * The parameters of the signal are a vector of bytes and a vector of file + * descriptors. + */ + +/** + * \var IPCPipe::connected_ + * \brief Flag to indicate if the IPCPipe instance is connected + * + * An IPCPipe instance is connected if IPC is successfully set up. + * + * This flag can be read via IPCPipe::isConnected(). + * + * Implementations of the IPCPipe class should set this flag upon successful + * construction. + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 1291f1b3..b7cfe0c2 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -30,6 +30,7 @@ libcamera_sources = files([ 'ipa_manager.cpp', 'ipa_module.cpp', 'ipa_proxy.cpp', + 'ipc_pipe.cpp', 'ipc_unixsocket.cpp', 'log.cpp', 'media_device.cpp', From patchwork Thu Dec 24 08:15:33 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10722 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 1C556C0F1A for ; Thu, 24 Dec 2020 08:16:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DC5D7615B4; Thu, 24 Dec 2020 09:16:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mbGad5hu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DB9CB60525 for ; Thu, 24 Dec 2020 09:16:05 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6083CDFC; Thu, 24 Dec 2020 09:16:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797765; bh=IYfQNzR8mIZkmiTHeyAdiatvHYrxWrwjMudNUov8NZc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mbGad5huUt45sAmt+Eim/XULYb8Xw9p9LRrRhyvUIZjYw+32pVr3WcReXxmtMvNhG l7TN7amnZ/8ciF6IiZTAyOj96nkxvsny2d8JqcN42MbBZ3NTbw22TvZn9NaeILu6DK g0zAE0Nc67B2XvgWpr3BthtAsVfDvL9V4zTpSGG0= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:33 +0900 Message-Id: <20201224081534.41601-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 8/9] libcamera: Add IPCPipe implementation based on unix socket X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an implementation of IPCPipe using unix socket. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- Changes in v6: - remove explicit nullptr intializations for unique_ptr members - move callData_.erase() to the call() error path Changes in v5: - rename IPAIPCUnixSocket to IPCPipeUnixSocket - rename ipa_ipc_unisocket.[(cpp),h] ipc_pipe_unixsocket.[(cpp),h] Changes in v4: - change snake_case to camelCase - change proc_ and socket_ to unique pointers - move inclusion of corresponding header to first in the include list - reserve message data and fds size (for sending) Changes in v3: - remove unused writeUInt32() and readUInt32() - remove redundant definition of IPAIPCUnixSocket::isValid() - remove & 0xff in writeHeader() - make readHeader, writeHeader, and eraseHeader static class functions of IPAIPCUnixSocket instead of globals Changes in v2: - specify in doxygen to skip generating documentation for IPAIPCUnixSocket --- Documentation/Doxyfile.in | 2 + .../libcamera/internal/ipc_pipe_unixsocket.h | 50 ++++++ src/libcamera/ipc_pipe_unixsocket.cpp | 147 ++++++++++++++++++ src/libcamera/meson.build | 1 + 4 files changed, 200 insertions(+) create mode 100644 include/libcamera/internal/ipc_pipe_unixsocket.h create mode 100644 src/libcamera/ipc_pipe_unixsocket.cpp diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in index b18b8e9c..36a0cef3 100644 --- a/Documentation/Doxyfile.in +++ b/Documentation/Doxyfile.in @@ -837,8 +837,10 @@ RECURSIVE = YES EXCLUDE = @TOP_SRCDIR@/include/libcamera/span.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \ + @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \ @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \ @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \ + @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \ @TOP_SRCDIR@/src/libcamera/pipeline/ \ @TOP_SRCDIR@/src/libcamera/proxy/ \ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ diff --git a/include/libcamera/internal/ipc_pipe_unixsocket.h b/include/libcamera/internal/ipc_pipe_unixsocket.h new file mode 100644 index 00000000..fea3179c --- /dev/null +++ b/include/libcamera/internal/ipc_pipe_unixsocket.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipc_pipe_unixsocket.h - Image Processing Algorithm IPC module using unix socket + */ +#ifndef __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__ +#define __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__ + +#include +#include +#include + +#include "libcamera/internal/ipc_pipe.h" +#include "libcamera/internal/ipc_unixsocket.h" + +namespace libcamera { + +class Process; + +class IPCPipeUnixSocket : public IPCPipe +{ +public: + IPCPipeUnixSocket(const char *ipaModulePath, const char *ipaProxyWorkerPath); + ~IPCPipeUnixSocket(); + + int sendSync(uint32_t cmd, + const IPCMessage &in, + IPCMessage *out = nullptr) override; + + int sendAsync(uint32_t cmd, const IPCMessage &data) override; + +private: + struct CallData { + IPCUnixSocket::Payload *response; + bool done; + }; + + void readyRead(IPCUnixSocket *socket); + int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response, uint32_t seq); + + uint32_t seq_; + std::unique_ptr proc_; + std::unique_ptr socket_; + std::map callData_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_IPC_UNIXSOCKET_H__ */ diff --git a/src/libcamera/ipc_pipe_unixsocket.cpp b/src/libcamera/ipc_pipe_unixsocket.cpp new file mode 100644 index 00000000..43c20e6b --- /dev/null +++ b/src/libcamera/ipc_pipe_unixsocket.cpp @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipc_pipe_unixsocket.cpp - Image Processing Algorithm IPC module using unix socket + */ + +#include "libcamera/internal/ipc_pipe_unixsocket.h" + +#include + +#include "libcamera/internal/event_dispatcher.h" +#include "libcamera/internal/ipc_pipe.h" +#include "libcamera/internal/ipc_unixsocket.h" +#include "libcamera/internal/log.h" +#include "libcamera/internal/process.h" +#include "libcamera/internal/thread.h" +#include "libcamera/internal/timer.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPCPipe) + +IPCPipeUnixSocket::IPCPipeUnixSocket(const char *ipaModulePath, + const char *ipaProxyWorkerPath) + : IPCPipe(), seq_(0) +{ + std::vector fds; + std::vector args; + args.push_back(ipaModulePath); + + socket_ = std::make_unique(); + int fd = socket_->create(); + if (fd < 0) { + LOG(IPCPipe, Error) << "Failed to create socket"; + return; + } + socket_->readyRead.connect(this, &IPCPipeUnixSocket::readyRead); + args.push_back(std::to_string(fd)); + fds.push_back(fd); + + proc_ = std::make_unique(); + int ret = proc_->start(ipaProxyWorkerPath, args, fds); + if (ret) { + LOG(IPCPipe, Error) + << "Failed to start proxy worker process"; + return; + } + + connected_ = true; +} + +IPCPipeUnixSocket::~IPCPipeUnixSocket() +{ +} + +int IPCPipeUnixSocket::sendSync(uint32_t cmd, + const IPCMessage &in, IPCMessage *out) +{ + IPCUnixSocket::Payload message, response; + + const IPCMessage::Header header{ cmd, ++seq_ }; + message = in.payload(&header); + + int ret = call(message, &response, seq_); + if (ret) { + LOG(IPCPipe, Error) << "Failed to call sync"; + return ret; + } + + if (out) + *out = IPCMessage(response); + + return 0; +} + +int IPCPipeUnixSocket::sendAsync(uint32_t cmd, const IPCMessage &data) +{ + const IPCMessage::Header header{ cmd, 0 }; + IPCUnixSocket::Payload message = data.payload(&header); + + int ret = socket_->send(message); + if (ret) { + LOG(IPCPipe, Error) << "Failed to call async"; + return ret; + } + + return 0; +} + +void IPCPipeUnixSocket::readyRead(IPCUnixSocket *socket) +{ + IPCUnixSocket::Payload message; + int ret = socket->receive(&message); + if (ret) { + LOG(IPCPipe, Error) << "Receive message failed" << ret; + return; + } + + /* \todo Use span to avoid the double copy when callData is found. */ + IPCMessage ipcMessage(message); + + auto callData = callData_.find(ipcMessage.header().cookie); + if (callData != callData_.end()) { + *callData->second.response = std::move(message); + callData->second.done = true; + return; + } + + /* Received unexpected data, this means it's a call from the IPA. */ + recv.emit(ipcMessage.header().cmd, ipcMessage); + + return; +} + +int IPCPipeUnixSocket::call(const IPCUnixSocket::Payload &message, + IPCUnixSocket::Payload *response, uint32_t cookie) +{ + Timer timeout; + int ret; + + callData_[cookie].response = response; + callData_[cookie].done = false; + + ret = socket_->send(message); + if (ret) { + callData_.erase(seq_); + return ret; + } + + timeout.start(2000); + while (!callData_[cookie].done) { + if (!timeout.isRunning()) { + LOG(IPCPipe, Error) << "Call timeout!"; + callData_.erase(cookie); + return -ETIMEDOUT; + } + + Thread::current()->eventDispatcher()->processEvents(); + } + + callData_.erase(cookie); + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index b7cfe0c2..cd3cf9e3 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -31,6 +31,7 @@ libcamera_sources = files([ 'ipa_module.cpp', 'ipa_proxy.cpp', 'ipc_pipe.cpp', + 'ipc_pipe_unixsocket.cpp', 'ipc_unixsocket.cpp', 'log.cpp', 'media_device.cpp', From patchwork Thu Dec 24 08:15:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10723 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 9D625C0F1A for ; Thu, 24 Dec 2020 08:16:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E3F962005; Thu, 24 Dec 2020 09:16:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rpHKFYFD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 166AF61FF6 for ; Thu, 24 Dec 2020 09:16:08 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 532DEDFC; Thu, 24 Dec 2020 09:16:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797767; bh=LOqr38i7O+YVn2Y3J9i1bCN9rXsqr7jSRXhLKHGgn/w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rpHKFYFDSOQwBBl5FZsYxEmaffdlHGI1tdd9+lPFiVPN0VFnSSQ0EqIQk3Chabey/ ebZ21KOLwy2npo8g1sXINiU9FLaQHViQP6JaaDtgyINyAxOz26lYkCDl1lXKOJvfKU c8C1UKa5+vdWdVZK02XjN+IDXFJHDxCB9eWv9A08= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:15:34 +0900 Message-Id: <20201224081534.41601-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081534.41601-1-paul.elder@ideasonboard.com> References: <20201224081534.41601-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 9/9] ipa: Add core.mojom X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a base mojom file to contain empty mojom definitions of libcamera objects, as well as documentation for the IPA interfaces that need to be defined in the mojom files. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- Changes in v6: - expand documentation on what can and can't be done in mojom - add definitions for geometry.h structs, and the structs that used to be in ipa_interface.h, including their documentation - remove documentation for start() Changes in v5: - add todo for defining some libcamera ipa structs in mojom - remove ipa_mojom_core from dependencies of everything in the generator stage - add documentation for the base IPA functions (init, stop, start) Changes in v4: - move docs to IPA guide Changes in v3: - add doc that structs need to be defined - add doc to recommend namespacing - change indentation - add requirement that base controls *must* be defined in libcamera::{pipeline_name}::Controls New in v2 --- include/libcamera/ipa/core.mojom | 208 +++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 include/libcamera/ipa/core.mojom diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom new file mode 100644 index 00000000..d707508b --- /dev/null +++ b/include/libcamera/ipa/core.mojom @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Things that can be defined here (and in other mojom files): + * - consts + * - enums + * - structs + * + * Attributes: + * - genHeader - structs only + * - designate that this struct needs a C++ header definition to be generated + * - not necessary if this struct is already defined in a C++ header + * - genSerdes - structs only + * - designate that this struct needs a (de)serializer to be generated + * - all fields need a (de)serializer to be defined, either hand-written + * in ipa_data_serializer.h, or designated here with genSerdes + * - hasFd - struct fields or empty structs only + * - designate that this field or empty struct contains a FileDescriptor + * + * Rules: + * - Any struct that is used in a struct definition in mojom must also be + * defined in mojom + * - If the struct has both a definition in a C++ header and a (de)serializer + * in ipa_data_serializer.h, then the struct shall be declared as empty + * - If the struct only has a definition in a C++ header, but no + * (de)serializer, then the struct definition should have the [genSerdes] + * attribute + * - If the struct has neither a definition in a C++ header nor a + * (de)serializer, then the struct definition should have both the + * [genHeader] and [genSerdes] attributes + * - Nested structures (eg. FrameBuffer::Plane) cannot be defined in mojom. + * - Avoid them, by defining them in a header in C++ and a (de)serializer in + * ipa_data_serializer.h + * - If a struct is in an array/map inside a struct, then the struct that is + * the member of the array/map does not need a mojom definition. + * - This can be used to embed nested structures. The C++ double dolon is + * replaced with a dot (eg. FrameBuffer::Plane -> FrameBuffer.Plane) + * - The struct must still be defined in a header in C++ and a (de)serializer + * implemented in ipa_data_serializer.h, as it cannot be defined in mojom + * - [genHeader] and [genSerdes] only work here in core.mojom. Any struct defined + * in other mojom files will implicitly have both attributes. + * - If a struct definition does not have genHeader, then the header where the + * struct is defined must be #included (or the struct forward-declared) in + * ipa_interface.h + * - If a field in a struct has a FileDescriptor, but is not explicitly + * defined so in mojom, then the field must be marked with the [hasFd] + * attribute. + */ +struct ControlInfoMap {}; +struct ControlList {}; +struct FileDescriptor {}; + +[genSerdes] struct Point { + int32 x; + int32 y; +}; + +[genSerdes] struct Size { + uint32 width; + uint32 height; +}; + +[genSerdes] struct SizeRange { + Size min; + Size max; + uint32 hStep; + uint32 vStep; +}; + +[genSerdes] struct Rectangle { + int32 x; + int32 y; + uint32 width; + uint32 height; +}; + +[genSerdes] struct CameraSensorInfo { + string model; + + uint32 bitsPerPixel; + + Size activeAreaSize; + Rectangle analogCrop; + Size outputSize; + + uint64 pixelRate; + uint32 lineLength; +}; + +/** + * \struct IPABuffer + * \brief Buffer information for the IPA interface + * + * The IPABuffer structure associates buffer memory with a unique ID. It is + * used to map buffers to the IPA with IPAInterface::mapBuffers(), after which + * buffers will be identified by their ID in the IPA interface. + */ + +/** + * \var IPABuffer::id + * \brief The buffer unique ID + * + * Buffers mapped to the IPA are identified by numerical unique IDs. The IDs + * are chosen by the pipeline handler to fulfil the following constraints: + * + * - IDs shall be positive integers different than zero + * - IDs shall be unique among all mapped buffers + * + * When buffers are unmapped with IPAInterface::unmapBuffers() their IDs are + * freed and may be reused for new buffer mappings. + */ + +/** + * \var IPABuffer::planes + * \brief The buffer planes description + * + * Stores the dmabuf handle and length for each plane of the buffer. + */ + +/** + * \class IPAInterface + * \brief C++ Interface for IPA implementation + * + * This pure virtual class defines a skeletal C++ API for IPA modules. + * Specializations of this class must be defined in a mojom file in + * include/libcamera/ipa/ (see the IPA Writers Guide for details + * on how to do so). + * + * Due to process isolation all arguments to the IPAInterface methods and + * signals may need to be transferred over IPC. The class thus uses serializable + * data types only. The IPA C++ interface defines custom data structures that + * mirror core libcamera structures when the latter are not suitable, such as + * IPAStream to carry StreamConfiguration data. + * + * Custom data structures may also be defined in the mojom file, in which case + * the (de)serialization will automatically be generated. If any other libcamera + * structures are to be used as parameters, then a de/serializer for them must + * be implemented in IPADataSerializer. + * + * The pipeline handler shall use the IPAManager to locate a compatible + * IPAInterface. The interface may then be used to interact with the IPA module. + */ +[genHeader, genSerdes] struct IPABuffer { + uint32 id; + [hasFd] array planes; +}; + +/** + * \struct IPASettings + * \brief IPA interface initialization settings + * + * The IPASettings structure stores data passed to the IPAInterface::init() + * function. The data contains settings that don't depend on a particular camera + * or pipeline configuration and are valid for the whole life time of the IPA + * interface. + */ + +/** + * \var IPASettings::configurationFile + * \brief The name of the IPA configuration file + * + * This field may be an empty string if the IPA doesn't require a configuration + * file. + */ +[genHeader, genSerdes] struct IPASettings { + string configurationFile; +}; + +/** + * \struct IPAStream + * \brief Stream configuration for the IPA interface + * + * The IPAStream structure stores stream configuration parameters needed by the + * IPAInterface::configure() method. It mirrors the StreamConfiguration class + * that is not suitable for this purpose due to not being serializable. + */ + +/** + * \var IPAStream::pixelFormat + * \brief The stream pixel format + */ + +/** + * \var IPAStream::size + * \brief The stream size in pixels + */ +[genHeader, genSerdes] struct IPAStream { + uint32 pixelFormat; + Size size; +}; + +/** + * \fn init() + * \brief Initialise the IPAInterface + * \param[in] settings The IPA initialization settings + * + * This function initializes the IPA interface. It shall be called before any + * other function of the IPAInterface. The \a settings carry initialization + * parameters that are valid for the whole life time of the IPA interface. + */ + +/** + * \fn stop() + * \brief Stop the IPA + * + * This method informs the IPA module that the camera is stopped. The IPA module + * shall release resources prepared in start(). + */