From patchwork Sat Dec 5 10:30:44 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10564 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 14887BDB20 for ; Sat, 5 Dec 2020 10:31:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D604E635F6; Sat, 5 Dec 2020 11:31:20 +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="ZYQUD6w6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 82241635F0 for ; Sat, 5 Dec 2020 11:31:18 +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 1E70499A; Sat, 5 Dec 2020 11:31:15 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164278; bh=Eva7yftyT6oau7gCTgcu6JM5Ka6GDDkqhfmAwUksuJA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZYQUD6w6cvcMkyk2PiE0Nqu8Dt3Ib/liazEgAQ1y+c0azfpkkfORBwH5YuG703oRc FbcJivYRLraCZcNFjeULc0AvtbWQeKP4LoWSZcixSw8ldZHhhieRvpI9LUYBDLiyrd hGblMqng2EcA+DZNctZ9meDZvsYfsq1YtBahG2XU= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:44 +0900 Message-Id: <20201205103106.242080-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 01/23] 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 --- Changes in v5: - add a usage output to the proxy worker, to document the interface for executing the proxy worker - in the mojom generator python script: - removed unused things (imports, functions, jinja exports) - document GetNameForElement - rename everything cb -> 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 --- .../module_ipa_interface.h.tmpl | 113 ++++ .../module_ipa_proxy.cpp.tmpl | 223 ++++++++ .../module_ipa_proxy.h.tmpl | 121 +++++ .../module_ipa_proxy_worker.cpp.tmpl | 224 ++++++++ .../module_ipa_serializer.h.tmpl | 47 ++ .../libcamera_templates/proxy_functions.tmpl | 204 ++++++++ .../libcamera_templates/serializer.tmpl | 289 +++++++++++ .../generators/mojom_libcamera_generator.py | 487 ++++++++++++++++++ 8 files changed, 1708 insertions(+) create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl new file mode 100644 index 00000000..9170153a --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl @@ -0,0 +1,113 @@ +{#- + # SPDX-License-Identifier: LGPL-2.1-or-later + # Copyright (C) 2020, Google Inc. +-#} +/* 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 %} + +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 %} +enum {{enum.mojom_name}} { +{%- for field in enum.fields %} + {{field.mojom_name}} = {{field.numeric_value}}, +{%- endfor %} +}; +{% endfor %} + +{%- for struct in structs_nonempty %} +struct {{struct.mojom_name}} +{ +public: + {{struct.mojom_name}}() {%- if struct|has_default_fields %} + :{% endif %} +{%- for field in struct.fields|with_default_values -%} +{{" " if loop.first}}{{field.mojom_name}}_({{field|default_value}}){{", " if not loop.last}} +{%- endfor %} + { + } + + {{struct.mojom_name}}( +{%- 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 %} +}; +{% endfor %} + +{#- +Any consts or #defines should be moved to the mojom file when possible. +If anything needs to be #included, then {{module_name}}.h needs to have the +#include. +#} +class {{interface_name}} : public IPAInterface +{ +public: +{% 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..f06aa973 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl @@ -0,0 +1,223 @@ +{#- + # 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. + * + * ipa_proxy_{{module_name}}.cpp - Image Processing Algorithm proxy for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include + +#include +#include +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/ipa_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("ipa_proxy_{{module_name}}"); + 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 == "start" %} + {{proxy_funcs.start_thread_body()}} +{%- elif method.mojom_name == "stop" %} + {{proxy_funcs.stop_thread_body()}} +{%- elif not method|is_async %} + ipa_->{{method.mojom_name}}( + {%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} + {%- endfor -%} +); +{% elif method|is_async %} + proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued, + {%- for param in method|method_param_names -%} + {{param}}{{- ", " if not loop.last}} + {%- endfor -%} +); +{%- endif %} +} + +{{proxy_funcs.func_sig(proxy_name, method, "IPC")}} +{ +{%- set has_input = true if method|method_param_inputs|length > 0 %} +{%- set has_output = true if method|method_param_outputs|length > 0 or method|method_return_value != "void" %} +{%- if has_input %} + IPCMessage _ipcInputBuf; +{%- endif %} +{%- if has_output %} + IPCMessage _ipcOutputBuf; +{%- endif %} + +{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf.data()', '_ipcInputBuf.fds()', base_controls)}} + +{%- 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..1ec3808b --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl @@ -0,0 +1,121 @@ +{#- + # 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. + * + * ipa_proxy_{{module_name}}.h - Image Processing Algorithm proxy for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__ +#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__ + +#include +#include +#include + +#include "libcamera/internal/control_serializer.h" +#include "libcamera/internal/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; + } + + int start() + { + return ipa_->start(); + } + + void stop() + { + ipa_->stop(); + } +{% for method in interface_main.methods %} +{%- if method|is_async %} + {{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}} + { + ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}); + } +{%- endif %} +{%- endfor %} + + private: + {{interface_name}} *ipa_; + }; + + bool running_; + Thread thread_; + ThreadProxy proxy_; + std::unique_ptr<{{interface_name}}> ipa_; + + const bool isolate_; + + std::unique_ptr ipc_; + + ControlSerializer controlSerializer_; +}; + +{%- 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..b7369793 --- /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. + * + * ipa_proxy_{{module_name}}_worker.cpp - Image Processing Algorithm proxy worker for {{module_name}} + * + * This file is auto-generated. Do not edit. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "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()", base_controls)|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()", base_controls)}} + + 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. */ +#if 0 + std::string logPath = "/tmp/libcamera.worker." + + std::to_string(getpid()) + ".log"; + logSetFile(logPath.c_str()); +#endif + + 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..b403131c --- /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}}_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, base_controls, namespace_str)}} +{%- if struct|has_fd %} +{{serializer.deserializer_fd(struct, namespace_str)}} +{%- else %} +{{serializer.deserializer_no_fd(struct, namespace_str)}} +{%- endif %} +}; +{% endfor %} + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__ */ diff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl new file mode 100644 index 00000000..661804b3 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl @@ -0,0 +1,204 @@ +{#- + # 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 start() function for thread + #} +{%- macro start_thread_body() -%} + running_ = true; + thread_.start(); + + return proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking); +{%- endmacro -%} + +{# + # \brief Generate function body for IPA stop() function for thread + #} +{%- macro stop_thread_body() -%} + if (!running_) + return; + + running_ = false; + + proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking); + + thread_.exit(); + thread_.wait(); +{%- endmacro -%} + + +{# + # \brief Serialize multiple objects into data buffer and fd vector + # + # Generate code to serialize multiple objects, as specified in \a params + # (which are the parameters to some function), into \a buf data buffer and + # \a fds fd vector. + # This code is meant to be used by the proxy, for serializing prior to IPC calls. + #} +{%- macro serialize_call(params, buf, fds, base_controls) %} +{%- for param in params %} + std::vector {{param.mojom_name}}Buf; +{%- if param|has_fd %} + std::vector {{param.mojom_name}}Fds; + std::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) = +{%- else %} + std::tie({{param.mojom_name}}Buf, std::ignore) = +{%- endif %} +{%- if param|is_controls %} + IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}, {{param.mojom_name}}.infoMap() ? *{{param.mojom_name}}.infoMap() : {{base_controls}} +{%- else %} + IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}} +{%- endif %} +{{- ", &controlSerializer_" if param|needs_control_serializer -}} +); +{%- endfor %} + +{%- if params|length > 1 %} +{%- for param in params %} + 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. + #} +{%- 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..333271e7 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl @@ -0,0 +1,289 @@ +{#- + # 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). \a base_controls indicates the + # default ControlInfoMap in the event that the ControlList does not have one. + # This code is meant to be used by the IPADataSerializer specialization. + #} +{%- macro serializer_field(field, base_controls, namespace, loop) %} +{%- if field|is_pod or field|is_enum %} + std::vector {{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}}_, + data.{{field.mojom_name}}_.infoMap() ? *data.{{field.mojom_name}}_.infoMap() : {{base_controls}}, + 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}}>(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); + {{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds')}} + m += {{field_size}}; + dataSize -= {{field_size}}; + n += {{field.mojom_name}}FdsSize; + fdsSize -= {{field.mojom_name}}FdsSize; + {%- 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. \a base_controls indicates the default ControlInfoMap + # in the event that the ControlList does not have one. + #} +{%- macro serializer(struct, base_controls, namespace) %} + static std::tuple, std::vector> + 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, base_controls, namespace, loop)}} +{%- endfor %} +{% if struct|has_fd %} + return {retData, retFds}; +{%- else %} + return {retData, {}}; +{%- endif %} + } +{%- endmacro %} + + +{# + # \brief Deserialize a struct that has fds + # + # Generate code for IPADataSerializer specialization, for deserializing + # \a struct, in the case that \a struct has file descriptors. + #} +{%- macro deserializer_fd(struct, namespace) %} + static {{struct|name_full(namespace)}} + deserialize(std::vector &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); + 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 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..1a056a05 --- /dev/null +++ b/utils/ipc/generators/mojom_libcamera_generator.py @@ -0,0 +1,487 @@ +#!/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): + return {} + attrs = [(x.attributes) for x in element.fields] + ret = {} + for d in attrs: + if d is not None: + ret = ret.update(d) + return ret + +def NeedsControlSerializer(element): + types = GetAllTypes(element) + return "ControlList" in types or "ControlInfoMap" in types + +def HasFd(element): + attrs = GetAllAttrs(element) + if isinstance(element, mojom.Kind): + types = GetAllTypes(element) + else: + types = GetAllTypes(element.kind) + return "FileDescriptor" in types or (attrs is not None and "hasFd" in attrs) + +def 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] + # strings that are members of vectors/maps + if (hasattr(element, 'spec')): + if (element.spec == 's'): + return 'std::string' + 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 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') + + # Validate parameters to start() + ValidateZeroLength(f_start.parameters, 'input parameter to start()') + ValidateSingleLength(f_start.response_parameters, 'output parameter from start()') + if f_start.response_parameters[0].kind.spec != 'i32': + raise Exception('start() must have single int32 output parameter') + + # Validate parameters to stop() + ValidateZeroLength(f_stop.parameters, 'input parameter to stop()') + ValidateZeroLength(f_stop.parameters, 'output parameter from stop()') + + # Validate that 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 { + 'base_controls': '%s::controls' % self.module_name, + 'cmd_enum_name': '_%sCmd' % self.module_name, + 'cmd_event_enum_name': '_%sEventCmd' % self.module_name, + '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], + } + + + @UseJinja('module_ipa_interface.h.tmpl') + def _GenerateDataHeader(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_serializer.h.tmpl') + def _GenerateSerializer(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy.cpp.tmpl') + def _GenerateProxyCpp(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy.h.tmpl') + def _GenerateProxyHeader(self): + return self._GetJinjaExports() + + @UseJinja('module_ipa_proxy_worker.cpp.tmpl') + def _GenerateProxyWorker(self): + return self._GetJinjaExports() + + def GenerateFiles(self, unparsed_args): + parser = argparse.ArgumentParser() + parser.add_argument('--libcamera_generate_header', action='store_true') + parser.add_argument('--libcamera_generate_serializer', action='store_true') + parser.add_argument('--libcamera_generate_proxy_cpp', action='store_true') + parser.add_argument('--libcamera_generate_proxy_h', action='store_true') + parser.add_argument('--libcamera_generate_proxy_worker', action='store_true') + parser.add_argument('--libcamera_output_path') + args = parser.parse_args(unparsed_args) + + ValidateInterfaces(self.module.interfaces) + + fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path)) + + self.module_name = ModuleClassName(self.module) + + if args.libcamera_generate_header: + self.Write(self._GenerateDataHeader(), + args.libcamera_output_path) + + if args.libcamera_generate_serializer: + self.Write(self._GenerateSerializer(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_cpp: + self.Write(self._GenerateProxyCpp(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_h: + self.Write(self._GenerateProxyHeader(), + args.libcamera_output_path) + + if args.libcamera_generate_proxy_worker: + self.Write(self._GenerateProxyWorker(), + args.libcamera_output_path) From patchwork Sat Dec 5 10:30:45 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10565 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 A0454BDB20 for ; Sat, 5 Dec 2020 10:31:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 69AD3635FE; Sat, 5 Dec 2020 11:31:22 +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="X1iZnkR1"; 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 83226635F6 for ; Sat, 5 Dec 2020 11:31:20 +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 C02D599A; Sat, 5 Dec 2020 11:31:18 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164280; bh=EDGQClJ7ZqFjy5MvSPVdyoYEpgPEv+0sU42zELEib2I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=X1iZnkR15lG2nOO/39qa5dvnqDjK5NW1sdZ3969O6mqU3SWz22wiR+vuSV/wNujle maAWIYls0FSBaBYZ3G8j8kJ8AO2CCOjMHKQAUN4soaoWHNLY71Ger0/ZN1QbPrEuqF SePk7gUtQ6ToS6CJovnV9W840YVvTfVfF6txcwR4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:45 +0900 Message-Id: <20201205103106.242080-3-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 02/23] 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 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 Sat Dec 5 10:30:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10566 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 F2CA4BDB20 for ; Sat, 5 Dec 2020 10:31:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C3C7E635F4; Sat, 5 Dec 2020 11:31:23 +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="PbFDKlt2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AC39D63600 for ; Sat, 5 Dec 2020 11:31:22 +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 DF9E62A4; Sat, 5 Dec 2020 11:31:20 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164282; bh=JRCCfZdRsX7oYSklQ2hLa4U0BHk3+ERyiyYV78dlU38=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PbFDKlt2RINs+BsSUYj3zYCvEj0M36+Fuzf05KHoO14M6bNhLFTHODMKTG7z0bpRN V322uvxrF6DozfsuAktcaacrO0A6mmf8L2iUUmDg4PkCEdb17rcaG+OBb+O4Io3/ij u1HnVUMIT2bp6sKmpkRvrmXq8+54FzbEhx8dq4Yc= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:46 +0900 Message-Id: <20201205103106.242080-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 03/23] 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 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 Sat Dec 5 10:30:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10567 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 56B52BDB20 for ; Sat, 5 Dec 2020 10:31:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 22FE1635F6; Sat, 5 Dec 2020 11:31:26 +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="eIhRJd5y"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 205A4635F0 for ; Sat, 5 Dec 2020 11:31:25 +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 1589599A; Sat, 5 Dec 2020 11:31:22 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164284; bh=pzH1Gk0FlpxNAbIjqfp+PFBSfOHiWOBuSPv2V6MlMW4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eIhRJd5y+X1cjBioTgZF1keHhX02pNndUaVY47jr42VXoywb53rpyvkwhW07s8v1C iHirVNHUb9UP0WGplt7XMLqRbL21I42SVcC5XQm5M5ywb5DQs5ODJ7ESOAeWo3rVNO wWd48YBbirvEOEe3g16nlOpvj8tIr8U0E2WxsPTM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:47 +0900 Message-Id: <20201205103106.242080-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 04/23] 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 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 Sat Dec 5 10:30:48 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10568 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 ABFA6BDB20 for ; Sat, 5 Dec 2020 10:31:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7709B63604; Sat, 5 Dec 2020 11:31:28 +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="IwuxuPBl"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4778F635F0 for ; Sat, 5 Dec 2020 11:31:27 +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 86AF72A4; Sat, 5 Dec 2020 11:31:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164287; bh=GLIw6co1jpOsWVqIllyMU8iWJvwOihMIb8ldxusN67s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IwuxuPBldzdIVqkDt1nzLyDtdyYQfPRI8a5LJej3PdA+AZc9vp/rVEemhuLgAYCZk zt1+v+PAALz01X5v+XGmzSqB8UMlaRwryaDejoO0ztPkjynGUW+S6HXLKPCKurackl VzL4bUp8RFljAF11vtQ1gvDViQxYv2CSxzJc3LSo= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:48 +0900 Message-Id: <20201205103106.242080-6-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 05/23] 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 --- 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 | 932 ++++++++++++++++++ src/libcamera/ipa_data_serializer.cpp | 178 ++++ src/libcamera/meson.build | 1 + 3 files changed, 1111 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..0394ab3a --- /dev/null +++ b/include/libcamera/internal/ipa_data_serializer.h @@ -0,0 +1,932 @@ +/* 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(); + } +}; + +/* + * IPASettings is serialized simply as a string. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const IPASettings &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return IPADataSerializer::serialize(data.configurationFile); + } + + static IPASettings deserialize(std::vector &data, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { IPADataSerializer::deserialize(data.cbegin(), data.end()) }; + } + + static IPASettings deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { IPADataSerializer::deserialize(dataBegin, dataEnd) }; + } + + static IPASettings deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return { IPADataSerializer::deserialize(data.cbegin(), data.end()) }; + } + + static IPASettings 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::deserialize(dataBegin, dataEnd) }; + } +}; + +/* + * CameraSensorInfo is serialized as a packed struct. The only exception is + * that the struct starts with 4 bytes for a uint32_t describing the length + * of the model name string. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const CameraSensorInfo &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + std::vector dataVec; + + uint32_t strLen = data.model.size(); + appendPOD(dataVec, strLen); + + dataVec.insert(dataVec.end(), data.model.cbegin(), data.model.end()); + + appendPOD(dataVec, data.bitsPerPixel); + + appendPOD(dataVec, data.activeAreaSize.width); + appendPOD(dataVec, data.activeAreaSize.height); + + appendPOD(dataVec, static_cast(data.analogCrop.x)); + appendPOD(dataVec, static_cast(data.analogCrop.y)); + appendPOD(dataVec, data.analogCrop.width); + appendPOD(dataVec, data.analogCrop.height); + + appendPOD(dataVec, data.outputSize.width); + appendPOD(dataVec, data.outputSize.height); + + appendPOD(dataVec, data.pixelRate); + + appendPOD(dataVec, data.lineLength); + + return { dataVec, {} }; + } + + static CameraSensorInfo deserialize(std::vector &data, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end()); + } + + static CameraSensorInfo deserialize(std::vector::const_iterator dataBegin, + [[maybe_unused]] std::vector::const_iterator dataEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + CameraSensorInfo ret; + + uint32_t strLen = readPOD(dataBegin, 0, dataEnd); + std::string str(dataBegin + 4, dataBegin + 4 + strLen); + ret.model = str; + + std::vector::const_iterator it = dataBegin + 4 + strLen; + + ret.bitsPerPixel = readPOD(it, 0, dataEnd); + + ret.activeAreaSize.width = readPOD(it, 4, dataEnd); + ret.activeAreaSize.height = readPOD(it, 8, dataEnd); + + ret.analogCrop.x = static_cast(readPOD(it, 12, dataEnd)); + ret.analogCrop.y = static_cast(readPOD(it, 16, dataEnd)); + ret.analogCrop.width = readPOD(it, 20, dataEnd); + ret.analogCrop.height = readPOD(it, 24, dataEnd); + + ret.outputSize.width = readPOD(it, 28, dataEnd); + ret.outputSize.height = readPOD(it, 32, dataEnd); + + ret.pixelRate = readPOD(it, 36, dataEnd); + + ret.lineLength = readPOD(it, 44, dataEnd); + + return ret; + } + + static CameraSensorInfo deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end()); + } + + static CameraSensorInfo 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); + } +}; + +/* + * IPAStream is serialized as a packed struct. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const IPAStream &data, [[maybe_unused]] ControlSerializer *cs = nullptr) + { + std::vector dataVec; + + appendPOD(dataVec, data.pixelFormat); + + appendPOD(dataVec, data.size.width); + appendPOD(dataVec, data.size.height); + + return { dataVec, {} }; + } + + static IPAStream deserialize(std::vector &data, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end()); + } + + static IPAStream deserialize(std::vector::const_iterator dataBegin, + [[maybe_unused]] std::vector::const_iterator dataEnd, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + IPAStream ret; + + ret.pixelFormat = readPOD(dataBegin, 0, dataEnd); + + ret.size.width = readPOD(dataBegin, 4, dataEnd); + ret.size.height = readPOD(dataBegin, 8, dataEnd); + + return ret; + } + + static IPAStream deserialize(std::vector &data, + [[maybe_unused]] std::vector &fds, + [[maybe_unused]] ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end()); + } + + static IPAStream 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); + } +}; + +/* + * 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) + */ +template<> +class IPADataSerializer +{ +public: + /* map arg will be generated, since it's per-pipeline anyway. */ + static std::tuple, std::vector> + serialize(const ControlList &data, const ControlInfoMap &map, + ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for serialization of ControlList"; + + 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 ControlList's ControlInfoMap"; + return { {}, {} }; + } + + size = cs->binarySize(data); + std::vector listData(size); + buffer = ByteStreamBuffer(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, + [[maybe_unused]] std::vector::const_iterator dataEnd, + ControlSerializer *cs) + { + if (!cs) + LOG(IPADataSerializer, Fatal) + << "ControlSerializer not provided for deserialization of ControlList"; + + 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); + + 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(); + } + + buffer = ByteStreamBuffer(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; + } +}; + +/* + * IPABuffer is serialized as a vector of FrameBuffer::Planes. + */ +template<> +class IPADataSerializer +{ +public: + static std::tuple, std::vector> + serialize(const IPABuffer &data, ControlSerializer *cs = nullptr) + { + std::vector dataVec; + + appendPOD(dataVec, data.id); + + std::vector planesDataVec; + std::vector planesFdsVec; + std::tie(planesDataVec, planesFdsVec) = + IPADataSerializer>::serialize(data.planes, cs); + + dataVec.insert(dataVec.end(), planesDataVec.begin(), planesDataVec.end()); + + return { dataVec, planesFdsVec }; + } + + static IPABuffer deserialize(std::vector &data, + std::vector &fds, + ControlSerializer *cs = nullptr) + { + return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs); + } + + static IPABuffer deserialize(std::vector::const_iterator dataBegin, + std::vector::const_iterator dataEnd, + std::vector::const_iterator fdsBegin, + std::vector::const_iterator fdsEnd, + ControlSerializer *cs = nullptr) + { + IPABuffer ret; + + ret.id = readPOD(dataBegin, 0, dataEnd); + + ret.planes = + IPADataSerializer>::deserialize( + dataBegin + 4, dataEnd, fdsBegin, fdsEnd, cs); + + 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..95fdddb3 --- /dev/null +++ b/src/libcamera/ipa_data_serializer.cpp @@ -0,0 +1,178 @@ +/* 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. + */ + +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 Sat Dec 5 10:30:49 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10569 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 01D95BDB20 for ; Sat, 5 Dec 2020 10:31:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C5A8263603; Sat, 5 Dec 2020 11:31:31 +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="VtlYRW2m"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F58A635F4 for ; Sat, 5 Dec 2020 11:31:29 +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 AA4112A4; Sat, 5 Dec 2020 11:31:27 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164289; bh=Qp4IsCKX1ywvnhlR45uHrqBOwRGuM3Li1dgErMeWwmY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VtlYRW2mN+ysLTOHjIVKYoBHfkxifyvQQPafQ/bd9cI7QylaDBOGUwhJSEr3zUPiT eJIz6iWNUKwRpTgCkpSr1Hpdap2ItxxDsOrTTf6/u+SXIX7kPV4IcFmNgG9mN4JsHR LfDjD8ABG3Lb+9+bBLGzx4/3kZs/MaY+agxhk7Os= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:49 +0900 Message-Id: <20201205103106.242080-7-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 06/23] 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 --- 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 0394ab3a..9d4aaba5 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 Sat Dec 5 10:30:50 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10570 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 4FF7CBDB20 for ; Sat, 5 Dec 2020 10:31:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1B60C63601; Sat, 5 Dec 2020 11:31:33 +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="IfbkMFMt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B333635FC for ; Sat, 5 Dec 2020 11:31:31 +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 0C7F599A; Sat, 5 Dec 2020 11:31:29 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164291; bh=SIJJdC3IOY5oADIaogTBIbubGZfPbbhuu84ht8RqGKo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IfbkMFMtNDXqusAhFEdWtE+SM0iTlyFlvgbm9pkMzS5Py35eOsA1vTiueAXbyvul9 nZPk8mVLHxY093g/wcDrXTZWCC8awALl39rLjoThQYbdDRhCnZhY9UXyBSK8AmNvYa h3eVWMXtfK5SmTgefaheFT7DKeutgKQ920ON+6EI= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:50 +0900 Message-Id: <20201205103106.242080-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 07/23] 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 --- 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..b47c47d2 --- /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), + proc_(nullptr), socket_(nullptr) +{ + 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"; + callData_.erase(seq_); + 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) + 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 Sat Dec 5 10:30:51 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10571 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 CF0FABDB20 for ; Sat, 5 Dec 2020 10:31:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9BDD263604; Sat, 5 Dec 2020 11:31:35 +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="eQOxU/6v"; 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 8B791635F9 for ; Sat, 5 Dec 2020 11:31:33 +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 DA7E42A4; Sat, 5 Dec 2020 11:31:31 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164293; bh=tQzrlE4fT7nsG87tFK4u6n8wkasxwFTnmmCe0xA5NWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eQOxU/6vs3ZTcSEBjyEG4xmwoY16kP45jKGqM193HTcmjZOiCiDCbR13/zLj43efL g/fjGz9mjjDtenHiA5oiCwKYBHaoDX1SfqOBW+wUNweIPs74VTujNGCPLUi3Z1qHQ1 Ms5ZlL7UEdMr59S/x3SNrlvttLjDcbiNB1zh1DiY= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:51 +0900 Message-Id: <20201205103106.242080-9-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 08/23] meson: ipa, proxy: Generate headers and proxy with mojo 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" Run mojo from meson to generate the header, serializer, and proxy files for every pipeline's mojom data definition file. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- No change in v5 Changes in v4: - rename module_generated.h.tmpl to module_ipa_interface.h.tmpl - rename module_serializer.h.tmpl to module_ipa_serializer.h.tmpl - rename libcamera_generated headers to libcamera_generated_ipa_headers in meson Changes in v3: - change parser path to our wrapper parser script, instead of running mojo's directly Changes in v2: - clean up loops - remove non-literal keys in maps - move loading the mojom scripts to utils/ - declare the template files in meson - add the generated headers into libcamera_generated_headers instead of libcamera_internal_headers - add libcamera_generated_headers to libcamera_sources --- include/libcamera/ipa/meson.build | 78 +++++++++++++++++++ src/libcamera/meson.build | 1 + src/libcamera/proxy/meson.build | 18 +++++ src/libcamera/proxy/worker/meson.build | 19 ++++- .../libcamera_templates/meson.build | 11 +++ utils/ipc/generators/meson.build | 3 + utils/ipc/meson.build | 13 ++++ 7 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 utils/ipc/generators/libcamera_templates/meson.build create mode 100644 utils/ipc/generators/meson.build diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index 508c6bd1..d7a2b6b2 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -8,3 +8,81 @@ libcamera_ipa_headers = files([ install_headers(libcamera_ipa_headers, subdir: join_paths(libcamera_include_dir, 'ipa')) + +# +# Prepare IPA/IPC generation components +# + +ipa_mojom_files = [] + +ipa_mojoms = [] + +# +# Generate headers from templates. +# + +libcamera_generated_ipa_headers = [] + +foreach file : ipa_mojom_files + name = file.split('.')[0] + + # {pipeline}.mojom-module + mojom = custom_target(file.split('.')[0] + '_mojom_module', + input : file, + output : file + '-module', + command : [ + mojom_parser, + '--output-root', meson.build_root(), + '--input-root', meson.source_root(), + '--mojoms', '@INPUT@' + ]) + + # {pipeline}_generated.h + header = custom_target(name + '_ipa_interface_h', + input : mojom, + output : name + '_ipa_interface.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_header', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + + # {pipeline}_serializer.h + serializer = custom_target(name + '_ipa_serializer_h', + input : mojom, + output : name + '_ipa_serializer.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_serializer', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + + # ipa_proxy_{pipeline}.h + proxy_header = custom_target(name + '_proxy_h', + input : mojom, + output : 'ipa_proxy_' + name + '.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_proxy_h', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + + ipa_mojoms += { + 'name': name, + 'mojom': mojom, + } + + libcamera_generated_ipa_headers += [header, serializer, proxy_header] +endforeach diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index cd3cf9e3..f7f3627c 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -59,6 +59,7 @@ libcamera_sources = files([ ]) libcamera_sources += libcamera_public_headers +libcamera_sources += libcamera_generated_ipa_headers libcamera_sources += libcamera_tracepoint_header includes = [ diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build index bd804750..ac68ad08 100644 --- a/src/libcamera/proxy/meson.build +++ b/src/libcamera/proxy/meson.build @@ -4,3 +4,21 @@ libcamera_sources += files([ 'ipa_proxy_linux.cpp', 'ipa_proxy_thread.cpp', ]) + +# generate ipa_proxy_{pipeline}.cpp +foreach mojom : ipa_mojoms + proxy = custom_target(mojom['name'] + '_proxy_cpp', + input : mojom['mojom'], + output : 'ipa_proxy_' + mojom['name'] + '.cpp', + depends : [mojom_templates], + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_proxy_cpp', + '--libcamera_output_path=@OUTPUT@', + './' + '@INPUT@' + ]) + + libcamera_sources += proxy +endforeach diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build index ac0310a7..8e0b978a 100644 --- a/src/libcamera/proxy/worker/meson.build +++ b/src/libcamera/proxy/worker/meson.build @@ -6,8 +6,23 @@ ipa_proxy_sources = [ proxy_install_dir = join_paths(get_option('libexecdir'), 'libcamera') -foreach t : ipa_proxy_sources - proxy = executable(t[0], t[1], +# generate ipa_proxy_{pipeline}_worker.cpp +foreach mojom : ipa_mojoms + worker = custom_target(mojom['name'] + '_proxy_worker', + input : mojom['mojom'], + output : 'ipa_proxy_' + mojom['name'] + '_worker.cpp', + depends : [mojom_templates], + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_proxy_worker', + '--libcamera_output_path=@OUTPUT@', + './' + '@INPUT@' + ]) + + proxy = executable('ipa_proxy_' + mojom['name'], + [worker, libcamera_generated_ipa_headers], install : true, install_dir : proxy_install_dir, dependencies : libcamera_dep) diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/ipc/generators/libcamera_templates/meson.build new file mode 100644 index 00000000..d7c9dea7 --- /dev/null +++ b/utils/ipc/generators/libcamera_templates/meson.build @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: CC0-1.0 + +mojom_template_files = files([ + '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/meson.build b/utils/ipc/generators/meson.build new file mode 100644 index 00000000..504f1a46 --- /dev/null +++ b/utils/ipc/generators/meson.build @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: CC0-1.0 + +subdir('libcamera_templates') diff --git a/utils/ipc/meson.build b/utils/ipc/meson.build index 59f097f0..4a41b9c1 100644 --- a/utils/ipc/meson.build +++ b/utils/ipc/meson.build @@ -1,3 +1,16 @@ # SPDX-License-Identifier: CC0-1.0 +subdir('generators') + py_modules += ['jinja2', 'ply'] + +mojom_parser = find_program('./parser.py') + +mojom_generator = find_program('./generate.py') + +mojom_templates = custom_target('mojom_templates', + input : mojom_template_files, + output : 'libcamera_templates.zip', + command : [mojom_generator, '-o', '@OUTDIR@', 'precompile']) + +mojom_templates_dir = meson.current_build_dir() From patchwork Sat Dec 5 10:30:52 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10572 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 5AF3CBDB20 for ; Sat, 5 Dec 2020 10:31:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 23B7A63601; Sat, 5 Dec 2020 11:31:37 +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="JGFv7/fv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BBE116360B for ; Sat, 5 Dec 2020 11:31:35 +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 0ADFA2A4; Sat, 5 Dec 2020 11:31:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164295; bh=Owq4ECoeU5w1MFk9rdQeg2zCRxlZEiWSqEfelfAfxdI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JGFv7/fvJORNmUjn93I5Kn7RrKosZBT1ckJZG62HM9f1GKomcL5kagqtVayoXOqn4 n0Kd34gUOuGdwV6WvEatZRdNSxsklXzbqkqr2JnxtAeO0OdJDwylZuuTSCq5X1Zwp+ RGJdz9pAKFIaLSmm/YHBveGdOl8WBCM87LxV7phg= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:52 +0900 Message-Id: <20201205103106.242080-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 09/23] tests: Remove IPA wrappers test 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" Since we will soon no longer use IPA wrappers, remove the test for it. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- Changes in v5: - move earlier in series No change in v4 No change in v3 New in v2 --- test/ipa/ipa_wrappers_test.cpp | 452 --------------------------------- test/ipa/meson.build | 1 - 2 files changed, 453 deletions(-) delete mode 100644 test/ipa/ipa_wrappers_test.cpp diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp deleted file mode 100644 index 59d991cb..00000000 --- a/test/ipa/ipa_wrappers_test.cpp +++ /dev/null @@ -1,452 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers - */ - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "libcamera/internal/camera_sensor.h" -#include "libcamera/internal/device_enumerator.h" -#include "libcamera/internal/ipa_context_wrapper.h" -#include "libcamera/internal/media_device.h" -#include "libcamera/internal/v4l2_subdevice.h" - -#include "test.h" - -using namespace libcamera; -using namespace std; - -enum Operation { - Op_init, - Op_start, - Op_stop, - Op_configure, - Op_mapBuffers, - Op_unmapBuffers, - Op_processEvent, -}; - -class TestIPAInterface : public IPAInterface -{ -public: - TestIPAInterface() - : sequence_(0) - { - } - - int init(const IPASettings &settings) override - { - if (settings.configurationFile != "/ipa/configuration/file") { - cerr << "init(): Invalid configuration file" << endl; - report(Op_init, TestFail); - return 0; - } - - report(Op_init, TestPass); - return 0; - } - - int start() override - { - report(Op_start, TestPass); - return 0; - } - - void stop() override - { - report(Op_stop, TestPass); - } - - void configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - [[maybe_unused]] const IPAOperationData &ipaConfig, - [[maybe_unused]] IPAOperationData *result) override - { - /* Verify sensorInfo. */ - if (sensorInfo.outputSize.width != 2560 || - sensorInfo.outputSize.height != 1940) { - cerr << "configure(): Invalid sensor info size " - << sensorInfo.outputSize.toString(); - } - - /* Verify streamConfig. */ - if (streamConfig.size() != 2) { - cerr << "configure(): Invalid number of streams " - << streamConfig.size() << endl; - return report(Op_configure, TestFail); - } - - auto iter = streamConfig.find(1); - if (iter == streamConfig.end()) { - cerr << "configure(): No configuration for stream 1" << endl; - return report(Op_configure, TestFail); - } - const IPAStream *stream = &iter->second; - if (stream->pixelFormat != V4L2_PIX_FMT_YUYV || - stream->size != Size{ 1024, 768 }) { - cerr << "configure(): Invalid configuration for stream 1" << endl; - return report(Op_configure, TestFail); - } - - iter = streamConfig.find(2); - if (iter == streamConfig.end()) { - cerr << "configure(): No configuration for stream 2" << endl; - return report(Op_configure, TestFail); - } - stream = &iter->second; - if (stream->pixelFormat != V4L2_PIX_FMT_NV12 || - stream->size != Size{ 800, 600 }) { - cerr << "configure(): Invalid configuration for stream 2" << endl; - return report(Op_configure, TestFail); - } - - /* Verify entityControls. */ - auto ctrlIter = entityControls.find(42); - if (ctrlIter == entityControls.end()) { - cerr << "configure(): Controls not found" << endl; - return report(Op_configure, TestFail); - } - - const ControlInfoMap &infoMap = ctrlIter->second; - - if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 || - infoMap.count(V4L2_CID_CONTRAST) != 1 || - infoMap.count(V4L2_CID_SATURATION) != 1) { - cerr << "configure(): Invalid control IDs" << endl; - return report(Op_configure, TestFail); - } - - report(Op_configure, TestPass); - } - - void mapBuffers(const std::vector &buffers) override - { - if (buffers.size() != 2) { - cerr << "mapBuffers(): Invalid number of buffers " - << buffers.size() << endl; - return report(Op_mapBuffers, TestFail); - } - - if (buffers[0].id != 10 || - buffers[1].id != 11) { - cerr << "mapBuffers(): Invalid buffer IDs" << endl; - return report(Op_mapBuffers, TestFail); - } - - if (buffers[0].planes.size() != 3 || - buffers[1].planes.size() != 3) { - cerr << "mapBuffers(): Invalid number of planes" << endl; - return report(Op_mapBuffers, TestFail); - } - - if (buffers[0].planes[0].length != 4096 || - buffers[0].planes[1].length != 0 || - buffers[0].planes[2].length != 0 || - buffers[0].planes[0].length != 4096 || - buffers[1].planes[1].length != 4096 || - buffers[1].planes[2].length != 0) { - cerr << "mapBuffers(): Invalid length" << endl; - return report(Op_mapBuffers, TestFail); - } - - if (buffers[0].planes[0].fd.fd() == -1 || - buffers[0].planes[1].fd.fd() != -1 || - buffers[0].planes[2].fd.fd() != -1 || - buffers[0].planes[0].fd.fd() == -1 || - buffers[1].planes[1].fd.fd() == -1 || - buffers[1].planes[2].fd.fd() != -1) { - cerr << "mapBuffers(): Invalid dmabuf" << endl; - return report(Op_mapBuffers, TestFail); - } - - report(Op_mapBuffers, TestPass); - } - - void unmapBuffers(const std::vector &ids) override - { - if (ids.size() != 2) { - cerr << "unmapBuffers(): Invalid number of ids " - << ids.size() << endl; - return report(Op_unmapBuffers, TestFail); - } - - if (ids[0] != 10 || ids[1] != 11) { - cerr << "unmapBuffers(): Invalid buffer IDs" << endl; - return report(Op_unmapBuffers, TestFail); - } - - report(Op_unmapBuffers, TestPass); - } - - void processEvent(const IPAOperationData &data) override - { - /* Verify operation and data. */ - if (data.operation != Op_processEvent) { - cerr << "processEvent(): Invalid operation " - << data.operation << endl; - return report(Op_processEvent, TestFail); - } - - if (data.data != std::vector{ 1, 2, 3, 4 }) { - cerr << "processEvent(): Invalid data" << endl; - return report(Op_processEvent, TestFail); - } - - /* Verify controls. */ - if (data.controls.size() != 1) { - cerr << "processEvent(): Controls not found" << endl; - return report(Op_processEvent, TestFail); - } - - const ControlList &controls = data.controls[0]; - if (controls.get(V4L2_CID_BRIGHTNESS).get() != 10 || - controls.get(V4L2_CID_CONTRAST).get() != 20 || - controls.get(V4L2_CID_SATURATION).get() != 30) { - cerr << "processEvent(): Invalid controls" << endl; - return report(Op_processEvent, TestFail); - } - - report(Op_processEvent, TestPass); - } - -private: - void report(Operation op, int status) - { - IPAOperationData data; - data.operation = op; - data.data.resize(1); - data.data[0] = status; - queueFrameAction.emit(sequence_++, data); - } - - unsigned int sequence_; -}; - -#define INVOKE(method, ...) \ - invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__) - -class IPAWrappersTest : public Test -{ -public: - IPAWrappersTest() - : subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1) - { - } - -protected: - int init() override - { - /* Locate the VIMC Sensor B subdevice. */ - enumerator_ = unique_ptr(DeviceEnumerator::create()); - if (!enumerator_) { - cerr << "Failed to create device enumerator" << endl; - return TestFail; - } - - if (enumerator_->enumerate()) { - cerr << "Failed to enumerate media devices" << endl; - return TestFail; - } - - DeviceMatch dm("vimc"); - media_ = enumerator_->search(dm); - if (!media_) { - cerr << "No VIMC media device found: skip test" << endl; - return TestSkip; - } - - MediaEntity *entity = media_->getEntityByName("Sensor A"); - if (!entity) { - cerr << "Unable to find media entity 'Sensor A'" << endl; - return TestFail; - } - - subdev_ = new V4L2Subdevice(entity); - if (subdev_->open() < 0) { - cerr << "Unable to open 'Sensor A' subdevice" << endl; - return TestFail; - } - - /* Force usage of the C API as that's what we want to test. */ - int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1); - if (ret) - return TestFail; - - std::unique_ptr intf = std::make_unique(); - wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf))); - wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction); - - /* Create a file descriptor for the buffer-related operations. */ - fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600); - if (fd_ == -1) - return TestFail; - - ret = ftruncate(fd_, 4096); - if (ret < 0) - return TestFail; - - return TestPass; - } - - int run() override - { - int ret; - - /* Test configure(). */ - CameraSensorInfo sensorInfo{ - .model = "sensor", - .bitsPerPixel = 8, - .activeAreaSize = { 2576, 1956 }, - .analogCrop = { 8, 8, 2560, 1940 }, - .outputSize = { 2560, 1940 }, - .pixelRate = 96000000, - .lineLength = 2918, - }; - std::map config{ - { 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } }, - { 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } }, - }; - std::map controlInfo; - controlInfo.emplace(42, subdev_->controls()); - IPAOperationData ipaConfig; - ret = INVOKE(configure, sensorInfo, config, controlInfo, - ipaConfig, nullptr); - if (ret == TestFail) - return TestFail; - - /* Test mapBuffers(). */ - std::vector buffers(2); - buffers[0].planes.resize(3); - buffers[0].id = 10; - buffers[0].planes[0].fd = FileDescriptor(fd_); - buffers[0].planes[0].length = 4096; - buffers[1].id = 11; - buffers[1].planes.resize(3); - buffers[1].planes[0].fd = FileDescriptor(fd_); - buffers[1].planes[0].length = 4096; - buffers[1].planes[1].fd = FileDescriptor(fd_); - buffers[1].planes[1].length = 4096; - - ret = INVOKE(mapBuffers, buffers); - if (ret == TestFail) - return TestFail; - - /* Test unmapBuffers(). */ - std::vector bufferIds = { 10, 11 }; - ret = INVOKE(unmapBuffers, bufferIds); - if (ret == TestFail) - return TestFail; - - /* Test processEvent(). */ - IPAOperationData data; - data.operation = Op_processEvent; - data.data = { 1, 2, 3, 4 }; - data.controls.emplace_back(subdev_->controls()); - - ControlList &controls = data.controls.back(); - controls.set(V4L2_CID_BRIGHTNESS, static_cast(10)); - controls.set(V4L2_CID_CONTRAST, static_cast(20)); - controls.set(V4L2_CID_SATURATION, static_cast(30)); - - ret = INVOKE(processEvent, data); - if (ret == TestFail) - return TestFail; - - /* - * Test init(), start() and stop() last to ensure nothing in the - * wrappers or serializer depends on them being called first. - */ - IPASettings settings{ - .configurationFile = "/ipa/configuration/file" - }; - ret = INVOKE(init, settings); - if (ret == TestFail) { - cerr << "Failed to run init()"; - return TestFail; - } - - ret = INVOKE(start); - if (ret == TestFail) { - cerr << "Failed to run start()"; - return TestFail; - } - - ret = INVOKE(stop); - if (ret == TestFail) { - cerr << "Failed to run stop()"; - return TestFail; - } - - return TestPass; - } - - void cleanup() override - { - delete wrapper_; - delete subdev_; - - if (fd_ != -1) - close(fd_); - } - -private: - template - int invoke(T (IPAInterface::*func)(Args1...), Operation op, - const char *name, Args2... args) - { - data_ = IPAOperationData(); - (wrapper_->*func)(args...); - - if (frame_ != sequence_) { - cerr << "IPAInterface::" << name - << "(): invalid frame number " << frame_ - << ", expected " << sequence_; - return TestFail; - } - - sequence_++; - - if (data_.operation != op) { - cerr << "IPAInterface::" << name - << "(): failed to propagate" << endl; - return TestFail; - } - - if (data_.data[0] != TestPass) { - cerr << "IPAInterface::" << name - << "(): reported an error" << endl; - return TestFail; - } - - return TestPass; - } - - void queueFrameAction(unsigned int frame, const IPAOperationData &data) - { - frame_ = frame; - data_ = data; - } - - std::shared_ptr media_; - std::unique_ptr enumerator_; - V4L2Subdevice *subdev_; - - IPAContextWrapper *wrapper_; - IPAOperationData data_; - unsigned int sequence_; - unsigned int frame_; - int fd_; -}; - -TEST_REGISTER(IPAWrappersTest) diff --git a/test/ipa/meson.build b/test/ipa/meson.build index ba672f3f..e4f0818a 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -3,7 +3,6 @@ ipa_test = [ ['ipa_module_test', 'ipa_module_test.cpp'], ['ipa_interface_test', 'ipa_interface_test.cpp'], - ['ipa_wrappers_test', 'ipa_wrappers_test.cpp'], ] foreach t : ipa_test From patchwork Sat Dec 5 10:30:53 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10573 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 ABE6EBDB20 for ; Sat, 5 Dec 2020 10:31:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 75FAC635F9; Sat, 5 Dec 2020 11:31:39 +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="Cl5h7gWF"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EBEF7635FB for ; Sat, 5 Dec 2020 11:31:37 +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 2FD8799A; Sat, 5 Dec 2020 11:31:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164297; bh=RRx9xl/91/IVTkOarnqD3GRyWNfIE1WRL6m37UNG23M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Cl5h7gWFRLrJj3RzhRv2NpzMejifu5/6+DzAWD6whgXKmUE5orJKigVAkBgev55Rh g0SWeHiYEWWL085v7HIoYaQ2l/I6Dg6sPG50LcapzNdG9WtxLay1PxCvQ5ARLQ7tEK rswGifvncWzmfmhomsn8M/CmNT9WkiMg21qPbAXA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:53 +0900 Message-Id: <20201205103106.242080-11-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 10/23] ipa: raspberrypi: meson: Add dependency on generated headers 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 raspberrypi IPA will depend on the raspberrypi IPA header generated from the mojo definition file. Add the dependency. Simply add all generated headers as a dependency, instead of walking the list of headers, to simplify the implementation. This will have the effect of generating all headers before compiling the IPA, which has no drawbacks. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- No change in v5 Changes in v4: - rename libcamera_generated_headers to libcamera_generated_ipa_headers Changes in v3: - expand changelog New in v2 --- src/ipa/raspberrypi/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/raspberrypi/meson.build b/src/ipa/raspberrypi/meson.build index 9445cd09..4331f718 100644 --- a/src/ipa/raspberrypi/meson.build +++ b/src/ipa/raspberrypi/meson.build @@ -42,7 +42,7 @@ rpi_ipa_sources = files([ ]) mod = shared_module(ipa_name, - rpi_ipa_sources, + [rpi_ipa_sources, libcamera_generated_ipa_headers], name_prefix : '', include_directories : rpi_ipa_includes, dependencies : rpi_ipa_deps, From patchwork Sat Dec 5 10:30:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10574 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 56E51BDB20 for ; Sat, 5 Dec 2020 10:31:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 24630635FE; Sat, 5 Dec 2020 11:31:43 +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="cqYj4k4R"; 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 4BD91635F2 for ; Sat, 5 Dec 2020 11:31:41 +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 546712A4; Sat, 5 Dec 2020 11:31:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164301; bh=+qhtveCJo7tYqPRF3N4sKW3prAjAAnSw9Ccl6jB6210=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cqYj4k4RNeUbBFeoD/ACDXYMVJ35BQeNbwAVluuEZthFT3On/nq4Q/htRBzye6HJw aMZeHN9pnncjGglkXlt5h/ij5q3cHncPqGJzKxsWOnqjZbeUQ3t2b+suZc617Z9XMA SPexQzMqbmbu5GqtXMfzxLmepi2UIH5SdIO+17YI= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:54 +0900 Message-Id: <20201205103106.242080-12-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 11/23] libcamera: IPAInterface: Replace C API with the new C++-only API 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" Remove everything related to the C API, including ipa_context, ipa_context_wrapper, and IPAInterfaceWrapper. Also remove relevant documentation. ipaCreate() provided by IPA implementations, and createInterface() provided by IPAModule (wrapper around IPA implementation) both now return a C++ object IPAInterface instead of struct ipa_context. Although IPAInterfaceWrapper is the only component of libipa, the skeleton and build files for libipa are retained. Signed-off-by: Paul Elder --- Squashed in v5 - change some words, remove some outdated points - don't remove libipa, only remove IPAInterfaceWrapper - as a result, libipa is now empty - update IPAInterface documentation - add todo on how to generate documentation for the new IPAInterface implementations No change in v4 No change in v3 Changes in v2: - add documentation for IPAModule::createInterface() --- This is a combination of 6 commits: --- libcamera: IPAModule: Replace ipa_context with IPAInterface With the new IPC infrastructure, we no longer need the C interface as provided by struct ipa_context. Make ipaCreate_() and createInterface() return IPAInterface. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: ipa_context_wrapper: Remove ipa_context_wrapper Since ipa_context has been replaced with custom IPAInterfaces, it is not longer needed. Remove it. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: IPAInterface: remove ipa_context and functions from documentation Remove all the documentation related to ipa_context and the C IPA API, as well as the documentation about the functions in the IPAInterface. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi --- libcamera: IPAInterface: Remove all functions from IPAInterface Now that all the functions in the IPA interface are defined in the data definition file and a specialized IPAInterface is generated per pipeline handler, remove all the functions from the base IPAInterface. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: IPAInterface: make ipaCreate return IPAInterface With the new IPC infrastructure, we no longer need the C interface as provided by struct ipa_context. Make ipaCreate return IPAinterface. Signed-off-by: Paul Elder Reviewed-by: Jacopo Mondi --- ipa: remove IPAInterfaceWrapper As every pipeline has its own proxy, IPAInterfaceWrapper is no longer necessary. Remove it. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Acked-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham Used to be "ipa: remove libipa" --- .../libcamera/internal/ipa_context_wrapper.h | 52 -- include/libcamera/internal/ipa_module.h | 4 +- include/libcamera/internal/meson.build | 1 - include/libcamera/ipa/ipa_interface.h | 126 +---- src/ipa/libipa/ipa_interface_wrapper.cpp | 285 ---------- src/ipa/libipa/ipa_interface_wrapper.h | 61 -- src/ipa/libipa/meson.build | 2 - src/ipa/raspberrypi/raspberrypi.cpp | 2 - src/ipa/rkisp1/rkisp1.cpp | 2 - src/ipa/vimc/vimc.cpp | 2 - src/libcamera/ipa_context_wrapper.cpp | 297 ---------- src/libcamera/ipa_interface.cpp | 534 ++---------------- src/libcamera/ipa_module.cpp | 18 +- src/libcamera/meson.build | 1 - 14 files changed, 56 insertions(+), 1331 deletions(-) delete mode 100644 include/libcamera/internal/ipa_context_wrapper.h delete mode 100644 src/ipa/libipa/ipa_interface_wrapper.cpp delete mode 100644 src/ipa/libipa/ipa_interface_wrapper.h delete mode 100644 src/libcamera/ipa_context_wrapper.cpp diff --git a/include/libcamera/internal/ipa_context_wrapper.h b/include/libcamera/internal/ipa_context_wrapper.h deleted file mode 100644 index 8f767e84..00000000 --- a/include/libcamera/internal/ipa_context_wrapper.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_context_wrapper.h - Image Processing Algorithm context wrapper - */ -#ifndef __LIBCAMERA_INTERNAL_IPA_CONTEXT_WRAPPER_H__ -#define __LIBCAMERA_INTERNAL_IPA_CONTEXT_WRAPPER_H__ - -#include - -#include "libcamera/internal/control_serializer.h" - -namespace libcamera { - -class IPAContextWrapper final : public IPAInterface -{ -public: - IPAContextWrapper(struct ipa_context *context); - ~IPAContextWrapper(); - - int init(const IPASettings &settings) override; - int start() override; - void stop() override; - void configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) override; - - void mapBuffers(const std::vector &buffers) override; - void unmapBuffers(const std::vector &ids) override; - - virtual void processEvent(const IPAOperationData &data) override; - -private: - static void queue_frame_action(void *ctx, unsigned int frame, - struct ipa_operation_data &data); - static const struct ipa_callback_ops callbacks_; - - void doQueueFrameAction(unsigned int frame, - const IPAOperationData &data); - - struct ipa_context *ctx_; - IPAInterface *intf_; - - ControlSerializer serializer_; -}; - -} /* namespace libcamera */ - -#endif /* __LIBCAMERA_INTERNAL_IPA_CONTEXT_WRAPPER_H__ */ diff --git a/include/libcamera/internal/ipa_module.h b/include/libcamera/internal/ipa_module.h index c2df2476..19fc5827 100644 --- a/include/libcamera/internal/ipa_module.h +++ b/include/libcamera/internal/ipa_module.h @@ -33,7 +33,7 @@ public: bool load(); - struct ipa_context *createContext(); + IPAInterface *createInterface(); bool match(PipelineHandler *pipe, uint32_t minVersion, uint32_t maxVersion) const; @@ -52,7 +52,7 @@ private: bool loaded_; void *dlHandle_; - typedef struct ipa_context *(*IPAIntfFactory)(); + typedef IPAInterface *(*IPAIntfFactory)(void); IPAIntfFactory ipaCreate_; }; diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 7cde023f..2c172ab3 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -23,7 +23,6 @@ libcamera_internal_headers = files([ 'event_notifier.h', 'file.h', 'formats.h', - 'ipa_context_wrapper.h', 'ipa_manager.h', 'ipa_module.h', 'ipa_proxy.h', diff --git a/include/libcamera/ipa/ipa_interface.h b/include/libcamera/ipa/ipa_interface.h index 322b7079..f6c40dc6 100644 --- a/include/libcamera/ipa/ipa_interface.h +++ b/include/libcamera/ipa/ipa_interface.h @@ -10,111 +10,6 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif - -struct ipa_context { - const struct ipa_context_ops *ops; -}; - -struct ipa_settings { - const char *configuration_file; -}; - -struct ipa_sensor_info { - const char *model; - uint8_t bits_per_pixel; - struct { - uint32_t width; - uint32_t height; - } active_area; - struct { - int32_t left; - int32_t top; - uint32_t width; - uint32_t height; - } analog_crop; - struct { - uint32_t width; - uint32_t height; - } output_size; - uint64_t pixel_rate; - uint32_t line_length; -}; - -struct ipa_stream { - unsigned int id; - unsigned int pixel_format; - unsigned int width; - unsigned int height; -}; - -struct ipa_control_info_map { - unsigned int id; - const uint8_t *data; - size_t size; -}; - -struct ipa_buffer_plane { - int dmabuf; - size_t length; -}; - -struct ipa_buffer { - unsigned int id; - unsigned int num_planes; - struct ipa_buffer_plane planes[3]; -}; - -struct ipa_control_list { - const uint8_t *data; - unsigned int size; -}; - -struct ipa_operation_data { - unsigned int operation; - const uint32_t *data; - unsigned int num_data; - const struct ipa_control_list *lists; - unsigned int num_lists; -}; - -struct ipa_callback_ops { - void (*queue_frame_action)(void *cb_ctx, unsigned int frame, - struct ipa_operation_data &data); -}; - -struct ipa_context_ops { - void (*destroy)(struct ipa_context *ctx); - void *(*get_interface)(struct ipa_context *ctx); - void (*init)(struct ipa_context *ctx, - const struct ipa_settings *settings); - int (*start)(struct ipa_context *ctx); - void (*stop)(struct ipa_context *ctx); - void (*register_callbacks)(struct ipa_context *ctx, - const struct ipa_callback_ops *callbacks, - void *cb_ctx); - void (*configure)(struct ipa_context *ctx, - const struct ipa_sensor_info *sensor_info, - const struct ipa_stream *streams, - unsigned int num_streams, - const struct ipa_control_info_map *maps, - unsigned int num_maps); - void (*map_buffers)(struct ipa_context *ctx, - const struct ipa_buffer *buffers, - size_t num_buffers); - void (*unmap_buffers)(struct ipa_context *ctx, const unsigned int *ids, - size_t num_buffers); - void (*process_event)(struct ipa_context *ctx, - const struct ipa_operation_data *data); -}; - -struct ipa_context *ipaCreate(); - -#ifdef __cplusplus -} - #include #include @@ -151,25 +46,12 @@ class IPAInterface { public: virtual ~IPAInterface() = default; - - virtual int init(const IPASettings &settings) = 0; - virtual int start() = 0; - virtual void stop() = 0; - - virtual void configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) = 0; - - virtual void mapBuffers(const std::vector &buffers) = 0; - virtual void unmapBuffers(const std::vector &ids) = 0; - - virtual void processEvent(const IPAOperationData &data) = 0; - Signal queueFrameAction; }; } /* namespace libcamera */ -#endif + +extern "C" { +libcamera::IPAInterface *ipaCreate(); +} #endif /* __LIBCAMERA_IPA_INTERFACE_H__ */ diff --git a/src/ipa/libipa/ipa_interface_wrapper.cpp b/src/ipa/libipa/ipa_interface_wrapper.cpp deleted file mode 100644 index cee532e3..00000000 --- a/src/ipa/libipa/ipa_interface_wrapper.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_interface_wrapper.cpp - Image Processing Algorithm interface wrapper - */ - -#include "ipa_interface_wrapper.h" - -#include -#include -#include -#include - -#include - -#include "libcamera/internal/byte_stream_buffer.h" -#include "libcamera/internal/camera_sensor.h" - -/** - * \file ipa_interface_wrapper.h - * \brief Image Processing Algorithm interface wrapper - */ - -namespace libcamera { - -/** - * \class IPAInterfaceWrapper - * \brief Wrap an IPAInterface and expose it as an ipa_context - * - * This class implements the ipa_context API based on a provided IPAInterface. - * It helps IPAs that implement the IPAInterface API to provide the external - * ipa_context API. - * - * To use the wrapper, an IPA module simple creates a new instance of its - * IPAInterface implementation, and passes it to the constructor of the - * IPAInterfaceWrapper. As IPAInterfaceWrapper inherits from ipa_context, the - * constructed wrapper can then be directly returned from the IPA module's - * ipaCreate() function. - * - * \code{.cpp} - * class MyIPA : public IPAInterface - * { - * ... - * }; - * - * struct ipa_context *ipaCreate() - * { - * return new IPAInterfaceWrapper(std::make_unique()); - * } - * \endcode - * - * The wrapper takes ownership of the IPAInterface and will automatically - * delete it when the wrapper is destroyed. - */ - -/** - * \brief Construct an IPAInterfaceWrapper wrapping \a interface - * \param[in] interface The interface to wrap - */ -IPAInterfaceWrapper::IPAInterfaceWrapper(std::unique_ptr interface) - : ipa_(std::move(interface)), callbacks_(nullptr), cb_ctx_(nullptr) -{ - ops = &operations_; - - ipa_->queueFrameAction.connect(this, &IPAInterfaceWrapper::queueFrameAction); -} - -void IPAInterfaceWrapper::destroy(struct ipa_context *_ctx) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - delete ctx; -} - -void *IPAInterfaceWrapper::get_interface(struct ipa_context *_ctx) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - return ctx->ipa_.get(); -} - -void IPAInterfaceWrapper::init(struct ipa_context *_ctx, - const struct ipa_settings *settings) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - IPASettings ipaSettings{ - .configurationFile = settings->configuration_file - }; - ctx->ipa_->init(ipaSettings); -} - -int IPAInterfaceWrapper::start(struct ipa_context *_ctx) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - return ctx->ipa_->start(); -} - -void IPAInterfaceWrapper::stop(struct ipa_context *_ctx) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - ctx->ipa_->stop(); -} - -void IPAInterfaceWrapper::register_callbacks(struct ipa_context *_ctx, - const struct ipa_callback_ops *callbacks, - void *cb_ctx) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - ctx->callbacks_ = callbacks; - ctx->cb_ctx_ = cb_ctx; -} - -void IPAInterfaceWrapper::configure(struct ipa_context *_ctx, - const struct ipa_sensor_info *sensor_info, - const struct ipa_stream *streams, - unsigned int num_streams, - const struct ipa_control_info_map *maps, - unsigned int num_maps) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - - ctx->serializer_.reset(); - - /* Translate the IPA sensor info. */ - CameraSensorInfo sensorInfo{}; - sensorInfo.model = sensor_info->model; - sensorInfo.bitsPerPixel = sensor_info->bits_per_pixel; - sensorInfo.activeAreaSize = { sensor_info->active_area.width, - sensor_info->active_area.height }; - sensorInfo.analogCrop = { sensor_info->analog_crop.left, - sensor_info->analog_crop.top, - sensor_info->analog_crop.width, - sensor_info->analog_crop.height }; - sensorInfo.outputSize = { sensor_info->output_size.width, - sensor_info->output_size.height }; - sensorInfo.pixelRate = sensor_info->pixel_rate; - sensorInfo.lineLength = sensor_info->line_length; - - /* Translate the IPA stream configurations map. */ - std::map ipaStreams; - - for (unsigned int i = 0; i < num_streams; ++i) { - const struct ipa_stream &stream = streams[i]; - - ipaStreams[stream.id] = { - stream.pixel_format, - Size(stream.width, stream.height), - }; - } - - /* Translate the IPA entity controls map. */ - std::map entityControls; - std::map infoMaps; - - for (unsigned int i = 0; i < num_maps; ++i) { - const struct ipa_control_info_map &ipa_map = maps[i]; - ByteStreamBuffer byteStream(ipa_map.data, ipa_map.size); - unsigned int id = ipa_map.id; - - infoMaps[id] = ctx->serializer_.deserialize(byteStream); - entityControls.emplace(id, infoMaps[id]); - } - - /* \todo Translate the ipaConfig and result. */ - IPAOperationData ipaConfig; - ctx->ipa_->configure(sensorInfo, ipaStreams, entityControls, ipaConfig, - nullptr); -} - -void IPAInterfaceWrapper::map_buffers(struct ipa_context *_ctx, - const struct ipa_buffer *_buffers, - size_t num_buffers) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - std::vector buffers(num_buffers); - - for (unsigned int i = 0; i < num_buffers; ++i) { - const struct ipa_buffer &_buffer = _buffers[i]; - IPABuffer &buffer = buffers[i]; - std::vector &planes = buffer.planes; - - buffer.id = _buffer.id; - - planes.resize(_buffer.num_planes); - for (unsigned int j = 0; j < _buffer.num_planes; ++j) { - planes[j].fd = FileDescriptor(_buffer.planes[j].dmabuf); - planes[j].length = _buffer.planes[j].length; - } - } - - ctx->ipa_->mapBuffers(buffers); -} - -void IPAInterfaceWrapper::unmap_buffers(struct ipa_context *_ctx, - const unsigned int *_ids, - size_t num_buffers) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - std::vector ids(_ids, _ids + num_buffers); - ctx->ipa_->unmapBuffers(ids); -} - -void IPAInterfaceWrapper::process_event(struct ipa_context *_ctx, - const struct ipa_operation_data *data) -{ - IPAInterfaceWrapper *ctx = static_cast(_ctx); - IPAOperationData opData; - - opData.operation = data->operation; - - opData.data.resize(data->num_data); - memcpy(opData.data.data(), data->data, - data->num_data * sizeof(*data->data)); - - opData.controls.resize(data->num_lists); - for (unsigned int i = 0; i < data->num_lists; ++i) { - const struct ipa_control_list *c_list = &data->lists[i]; - ByteStreamBuffer byteStream(c_list->data, c_list->size); - opData.controls[i] = ctx->serializer_.deserialize(byteStream); - } - - ctx->ipa_->processEvent(opData); -} - -void IPAInterfaceWrapper::queueFrameAction(unsigned int frame, - const IPAOperationData &data) -{ - if (!callbacks_) - return; - - struct ipa_operation_data c_data; - c_data.operation = data.operation; - c_data.data = data.data.data(); - c_data.num_data = data.data.size(); - - struct ipa_control_list control_lists[data.controls.size()]; - c_data.lists = control_lists; - c_data.num_lists = data.controls.size(); - - std::size_t listsSize = 0; - for (const auto &list : data.controls) - listsSize += serializer_.binarySize(list); - - std::vector binaryData(listsSize); - ByteStreamBuffer byteStreamBuffer(binaryData.data(), listsSize); - - unsigned int i = 0; - for (const auto &list : data.controls) { - struct ipa_control_list &c_list = control_lists[i]; - c_list.size = serializer_.binarySize(list); - - ByteStreamBuffer b = byteStreamBuffer.carveOut(c_list.size); - serializer_.serialize(list, b); - - c_list.data = b.base(); - } - - callbacks_->queue_frame_action(cb_ctx_, frame, c_data); -} - -#ifndef __DOXYGEN__ -/* - * This construct confuses Doygen and makes it believe that all members of the - * operations is a member of IPAInterfaceWrapper. It must thus be hidden. - */ -const struct ipa_context_ops IPAInterfaceWrapper::operations_ = { - .destroy = &IPAInterfaceWrapper::destroy, - .get_interface = &IPAInterfaceWrapper::get_interface, - .init = &IPAInterfaceWrapper::init, - .start = &IPAInterfaceWrapper::start, - .stop = &IPAInterfaceWrapper::stop, - .register_callbacks = &IPAInterfaceWrapper::register_callbacks, - .configure = &IPAInterfaceWrapper::configure, - .map_buffers = &IPAInterfaceWrapper::map_buffers, - .unmap_buffers = &IPAInterfaceWrapper::unmap_buffers, - .process_event = &IPAInterfaceWrapper::process_event, -}; -#endif - -} /* namespace libcamera */ diff --git a/src/ipa/libipa/ipa_interface_wrapper.h b/src/ipa/libipa/ipa_interface_wrapper.h deleted file mode 100644 index a1c70159..00000000 --- a/src/ipa/libipa/ipa_interface_wrapper.h +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_interface_wrapper.h - Image Processing Algorithm interface wrapper - */ -#ifndef __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__ -#define __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__ - -#include - -#include - -#include "libcamera/internal/control_serializer.h" - -namespace libcamera { - -class IPAInterfaceWrapper : public ipa_context -{ -public: - IPAInterfaceWrapper(std::unique_ptr interface); - -private: - static void destroy(struct ipa_context *ctx); - static void *get_interface(struct ipa_context *ctx); - static void init(struct ipa_context *ctx, - const struct ipa_settings *settings); - static int start(struct ipa_context *ctx); - static void stop(struct ipa_context *ctx); - static void register_callbacks(struct ipa_context *ctx, - const struct ipa_callback_ops *callbacks, - void *cb_ctx); - static void configure(struct ipa_context *ctx, - const struct ipa_sensor_info *sensor_info, - const struct ipa_stream *streams, - unsigned int num_streams, - const struct ipa_control_info_map *maps, - unsigned int num_maps); - static void map_buffers(struct ipa_context *ctx, - const struct ipa_buffer *c_buffers, - size_t num_buffers); - static void unmap_buffers(struct ipa_context *ctx, - const unsigned int *ids, - size_t num_buffers); - static void process_event(struct ipa_context *ctx, - const struct ipa_operation_data *data); - - static const struct ipa_context_ops operations_; - - void queueFrameAction(unsigned int frame, const IPAOperationData &data); - - std::unique_ptr ipa_; - const struct ipa_callback_ops *callbacks_; - void *cb_ctx_; - - ControlSerializer serializer_; -}; - -} /* namespace libcamera */ - -#endif /* __LIBCAMERA_IPA_INTERFACE_WRAPPER_H__ */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 22626405..b29ef0f4 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -1,11 +1,9 @@ # SPDX-License-Identifier: CC0-1.0 libipa_headers = files([ - 'ipa_interface_wrapper.h', ]) libipa_sources = files([ - 'ipa_interface_wrapper.cpp', ]) libipa_includes = include_directories('..') diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp index 29d48b1b..4dc02dc8 100644 --- a/src/ipa/raspberrypi/raspberrypi.cpp +++ b/src/ipa/raspberrypi/raspberrypi.cpp @@ -22,8 +22,6 @@ #include #include -#include - #include "libcamera/internal/buffer.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/log.h" diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 07d7f1b2..10301a19 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -22,8 +22,6 @@ #include #include -#include - #include "libcamera/internal/log.h" namespace libcamera { diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp index cf841135..483aeadd 100644 --- a/src/ipa/vimc/vimc.cpp +++ b/src/ipa/vimc/vimc.cpp @@ -17,8 +17,6 @@ #include #include -#include - #include "libcamera/internal/file.h" #include "libcamera/internal/log.h" diff --git a/src/libcamera/ipa_context_wrapper.cpp b/src/libcamera/ipa_context_wrapper.cpp deleted file mode 100644 index 231300ce..00000000 --- a/src/libcamera/ipa_context_wrapper.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_context_wrapper.cpp - Image Processing Algorithm context wrapper - */ - -#include "libcamera/internal/ipa_context_wrapper.h" - -#include - -#include - -#include "libcamera/internal/byte_stream_buffer.h" -#include "libcamera/internal/camera_sensor.h" -#include "libcamera/internal/utils.h" - -/** - * \file ipa_context_wrapper.h - * \brief Image Processing Algorithm context wrapper - */ - -namespace libcamera { - -/** - * \class IPAContextWrapper - * \brief Wrap an ipa_context and expose it as an IPAInterface - * - * The IPAContextWrapper class wraps an ipa_context, provided by an IPA module, and - * exposes an IPAInterface. This mechanism is used for IPAs that are not - * isolated in a separate process to allow direct calls from pipeline handler - * using the IPAInterface API instead of the lower-level ipa_context API. - * - * The IPAInterface methods are converted to the ipa_context API by translating - * all C++ arguments into plain C structures or byte arrays that contain no - * pointer, as required by the ipa_context API. - */ - -/** - * \brief Construct an IPAContextWrapper instance that wraps the \a context - * \param[in] context The IPA module context - * - * Ownership of the \a context is passed to the IPAContextWrapper. The context remains - * valid for the whole lifetime of the wrapper and is destroyed automatically - * with it. - */ -IPAContextWrapper::IPAContextWrapper(struct ipa_context *context) - : ctx_(context), intf_(nullptr) -{ - if (!ctx_) - return; - - bool forceCApi = !!utils::secure_getenv("LIBCAMERA_IPA_FORCE_C_API"); - - if (!forceCApi && ctx_ && ctx_->ops->get_interface) { - intf_ = reinterpret_cast(ctx_->ops->get_interface(ctx_)); - intf_->queueFrameAction.connect(this, &IPAContextWrapper::doQueueFrameAction); - return; - } - - ctx_->ops->register_callbacks(ctx_, &IPAContextWrapper::callbacks_, - this); -} - -IPAContextWrapper::~IPAContextWrapper() -{ - if (!ctx_) - return; - - ctx_->ops->destroy(ctx_); -} - -int IPAContextWrapper::init(const IPASettings &settings) -{ - if (intf_) - return intf_->init(settings); - - if (!ctx_) - return 0; - - struct ipa_settings c_settings; - c_settings.configuration_file = settings.configurationFile.c_str(); - - ctx_->ops->init(ctx_, &c_settings); - - return 0; -} - -int IPAContextWrapper::start() -{ - if (intf_) - return intf_->start(); - - if (!ctx_) - return 0; - - return ctx_->ops->start(ctx_); -} - -void IPAContextWrapper::stop() -{ - if (intf_) - return intf_->stop(); - - if (!ctx_) - return; - - ctx_->ops->stop(ctx_); -} - -void IPAContextWrapper::configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) -{ - if (intf_) - return intf_->configure(sensorInfo, streamConfig, - entityControls, ipaConfig, result); - - if (!ctx_) - return; - - serializer_.reset(); - - /* Translate the camera sensor info. */ - struct ipa_sensor_info sensor_info = {}; - sensor_info.model = sensorInfo.model.c_str(); - sensor_info.bits_per_pixel = sensorInfo.bitsPerPixel; - sensor_info.active_area.width = sensorInfo.activeAreaSize.width; - sensor_info.active_area.height = sensorInfo.activeAreaSize.height; - sensor_info.analog_crop.left = sensorInfo.analogCrop.x; - sensor_info.analog_crop.top = sensorInfo.analogCrop.y; - sensor_info.analog_crop.width = sensorInfo.analogCrop.width; - sensor_info.analog_crop.height = sensorInfo.analogCrop.height; - sensor_info.output_size.width = sensorInfo.outputSize.width; - sensor_info.output_size.height = sensorInfo.outputSize.height; - sensor_info.pixel_rate = sensorInfo.pixelRate; - sensor_info.line_length = sensorInfo.lineLength; - - /* Translate the IPA stream configurations map. */ - struct ipa_stream c_streams[streamConfig.size()]; - - unsigned int i = 0; - for (const auto &stream : streamConfig) { - struct ipa_stream *c_stream = &c_streams[i]; - unsigned int id = stream.first; - const IPAStream &ipaStream = stream.second; - - c_stream->id = id; - c_stream->pixel_format = ipaStream.pixelFormat; - c_stream->width = ipaStream.size.width; - c_stream->height = ipaStream.size.height; - - ++i; - } - - /* Translate the IPA entity controls map. */ - struct ipa_control_info_map c_info_maps[entityControls.size()]; - std::vector> data(entityControls.size()); - - i = 0; - for (const auto &info : entityControls) { - struct ipa_control_info_map &c_info_map = c_info_maps[i]; - unsigned int id = info.first; - const ControlInfoMap &infoMap = info.second; - - size_t infoMapSize = serializer_.binarySize(infoMap); - data[i].resize(infoMapSize); - ByteStreamBuffer byteStream(data[i].data(), data[i].size()); - serializer_.serialize(infoMap, byteStream); - - c_info_map.id = id; - c_info_map.data = byteStream.base(); - c_info_map.size = byteStream.size(); - - ++i; - } - - /* \todo Translate the ipaConfig and reponse */ - ctx_->ops->configure(ctx_, &sensor_info, c_streams, streamConfig.size(), - c_info_maps, entityControls.size()); -} - -void IPAContextWrapper::mapBuffers(const std::vector &buffers) -{ - if (intf_) - return intf_->mapBuffers(buffers); - - if (!ctx_) - return; - - struct ipa_buffer c_buffers[buffers.size()]; - - for (unsigned int i = 0; i < buffers.size(); ++i) { - struct ipa_buffer &c_buffer = c_buffers[i]; - const IPABuffer &buffer = buffers[i]; - const std::vector &planes = buffer.planes; - - c_buffer.id = buffer.id; - c_buffer.num_planes = planes.size(); - - for (unsigned int j = 0; j < planes.size(); ++j) { - const FrameBuffer::Plane &plane = planes[j]; - c_buffer.planes[j].dmabuf = plane.fd.fd(); - c_buffer.planes[j].length = plane.length; - } - } - - ctx_->ops->map_buffers(ctx_, c_buffers, buffers.size()); -} - -void IPAContextWrapper::unmapBuffers(const std::vector &ids) -{ - if (intf_) - return intf_->unmapBuffers(ids); - - if (!ctx_) - return; - - ctx_->ops->unmap_buffers(ctx_, ids.data(), ids.size()); -} - -void IPAContextWrapper::processEvent(const IPAOperationData &data) -{ - if (intf_) - return intf_->processEvent(data); - - if (!ctx_) - return; - - struct ipa_operation_data c_data; - c_data.operation = data.operation; - c_data.data = data.data.data(); - c_data.num_data = data.data.size(); - - struct ipa_control_list control_lists[data.controls.size()]; - c_data.lists = control_lists; - c_data.num_lists = data.controls.size(); - - std::size_t listsSize = 0; - for (const auto &list : data.controls) - listsSize += serializer_.binarySize(list); - - std::vector binaryData(listsSize); - ByteStreamBuffer byteStreamBuffer(binaryData.data(), listsSize); - - unsigned int i = 0; - for (const auto &list : data.controls) { - struct ipa_control_list &c_list = control_lists[i]; - c_list.size = serializer_.binarySize(list); - ByteStreamBuffer b = byteStreamBuffer.carveOut(c_list.size); - - serializer_.serialize(list, b); - - c_list.data = b.base(); - } - - ctx_->ops->process_event(ctx_, &c_data); -} - -void IPAContextWrapper::doQueueFrameAction(unsigned int frame, - const IPAOperationData &data) -{ - IPAInterface::queueFrameAction.emit(frame, data); -} - -void IPAContextWrapper::queue_frame_action(void *ctx, unsigned int frame, - struct ipa_operation_data &data) -{ - IPAContextWrapper *_this = static_cast(ctx); - IPAOperationData opData; - - opData.operation = data.operation; - for (unsigned int i = 0; i < data.num_data; ++i) - opData.data.push_back(data.data[i]); - - for (unsigned int i = 0; i < data.num_lists; ++i) { - const struct ipa_control_list &c_list = data.lists[i]; - ByteStreamBuffer b(c_list.data, c_list.size); - opData.controls.push_back(_this->serializer_.deserialize(b)); - } - - _this->doQueueFrameAction(frame, opData); -} - -#ifndef __DOXYGEN__ -/* - * This construct confuses Doxygen and makes it believe that all members of the - * operations is a member of IPAContextWrapper. It must thus be hidden. - */ -const struct ipa_callback_ops IPAContextWrapper::callbacks_ = { - .queue_frame_action = &IPAContextWrapper::queue_frame_action, -}; -#endif - -} /* namespace libcamera */ diff --git a/src/libcamera/ipa_interface.cpp b/src/libcamera/ipa_interface.cpp index 23fc56d7..edbfd979 100644 --- a/src/libcamera/ipa_interface.cpp +++ b/src/libcamera/ipa_interface.cpp @@ -15,371 +15,51 @@ * an Image Processing Algorithm (IPA) module. An IPA module is developed for a * specific pipeline handler and each pipeline handler may be compatible with * multiple IPA implementations, both open and closed source. To support this, - * libcamera communicates with IPA modules through a standard plain C interface. + * libcamera communicates with IPA modules through a per-pipeline C++ interface. * - * IPA modules shall expose a public function named ipaCreate() with the - * following prototype. + * IPA modules shall provide an ipaCreate() function exported as a public C + * symbol with the following prototype: * * \code{.c} - * struct ipa_context *ipaCreate(); + * IPAInterface *ipaCreate(); * \endcode * - * The ipaCreate() function creates an instance of an IPA context, which models + * The ipaCreate() function creates an instance of an IPA interface, which models * a context of execution for the IPA. IPA modules shall support creating one * context per camera, as required by their associated pipeline handler. * - * The IPA module context operations are defined in the struct ipa_context_ops. - * They model a low-level interface to configure the IPA, notify it of events, - * and receive IPA actions through callbacks. An IPA module stores a pointer to - * the operations corresponding to its context in the ipa_context::ops field. - * That pointer is immutable for the lifetime of the context, and may differ - * between different contexts created by the same IPA module. + * The IPA module interface operations are defined in the mojom file + * corresponding to the pipeline handler, in + * include/libcamera/ipa/{pipeline_name}.mojom. * - * The IPA interface defines base data types and functions to exchange data. On - * top of this, each pipeline handler is responsible for defining the set of - * events and actions used to communicate with their IPA. These are collectively - * referred to as IPA operations and define the pipeline handler-specific IPA - * protocol. Each operation defines the data that it carries, and how that data - * is encoded in the ipa_context_ops functions arguments. + * The IPA interface is specific to each pipeline handler. The pipeline handlers + * define a set of operations used to communicate with their IPA modules. The + * operations, along with the data structures they use, are collectively + * referred to as the IPA protocol. + * + * The IPA protocol is defined using the + * Mojo interface definition language, + * in a Mojo module file stored in include/libcamera/ipa/{pipeline_name}.mojom. + * The Mojo module contains two Mojo interfaces: IPAInterface defines the + * operations exposed by the IPA and called by the pipeline handler, and + * IPAEventInterface defines the events generated by the IPA and received by the + * pipeline handler. * * \todo Add reference to how pipelines shall document their protocol. * * IPAs can be isolated in a separate process. This implies that arguments to - * the IPA interface functions may need to be transferred over IPC. All - * arguments use Plain Old Data types and are documented either in the form of C - * data types, or as a textual description of byte arrays for types that can't - * be expressed using C data types (such as arrays of mixed data types). IPA - * modules can thus use the C API without calling into libcamera to access the - * data passed to the IPA context operations. + * the IPA interface functions may need to be transferred over IPC. An IPA + * proxy is auto-generated based on the mojom file, which abstracts away the + * (de)serialization from the pipeline handler and the IPA implementation. Thus + * any C++ structure that is defined in the mojom file, or the C++ libcamera + * objects that are listed in core.mojom, can be used directly. * * Due to IPC, synchronous communication between pipeline handlers and IPAs can - * be costly. For that reason, the interface operates asynchronously. This - * implies that methods don't return a status, and that all methods may copy - * their arguments. - * - * The IPAInterface class is a C++ representation of the ipa_context_ops, using - * C++ data classes provided by libcamera. This is the API exposed to pipeline - * handlers to communicate with IPA modules. IPA modules may use the - * IPAInterface API internally if they want to benefit from the data and helper - * classes offered by libcamera. - * - * When an IPA module is loaded directly into the libcamera process and uses - * the IPAInterface API internally, short-circuiting the path to the - * ipa_context_ops and back to IPAInterface is desirable. To support this, IPA - * modules may implement the ipa_context_ops::get_interface function to return a - * pointer to their internal IPAInterface. - */ - -/** - * \struct ipa_context - * \brief IPA module context of execution - * - * This structure models a context of execution for an IPA module. It is - * instantiated by the IPA module ipaCreate() function. IPA modules allocate - * context instances in an implementation-defined way, contexts shall thus be - * destroyed using the ipa_operation::destroy function only. - * - * The ipa_context structure provides a pointer to the IPA context operations. - * It shall otherwise be treated as a constant black-box cookie and passed - * unmodified to the functions defined in struct ipa_context_ops. - * - * IPA modules are expected to extend struct ipa_context by inheriting from it, - * either through structure embedding to model inheritance in plain C, or - * through C++ class inheritance. A simple example of the latter is available - * in the IPAContextWrapper class implementation. - * - * \var ipa_context::ops - * \brief The IPA context operations - */ - -/** - * \struct ipa_settings - * \brief IPA initialization settings for the IPA context operations - * \sa IPASettings - * - * \var ipa_settings::configuration_file - * \brief The name of the IPA configuration file (may be null or point to an - * empty string) - */ - -/** - * \struct ipa_sensor_info - * \brief Camera sensor information for the IPA context operations - * \sa libcamera::CameraSensorInfo - * - * \var ipa_sensor_info::model - * \brief The camera sensor model name - * \todo Remove this field as soon as no IPA depends on it anymore - * - * \var ipa_sensor_info::bits_per_pixel - * \brief The camera sensor image format bit depth - * \sa libcamera::CameraSensorInfo::bitsPerPixel - * - * \var ipa_sensor_info::active_area.width - * \brief The camera sensor pixel array active area width - * \sa libcamera::CameraSensorInfo::activeAreaSize - * - * \var ipa_sensor_info::active_area.height - * \brief The camera sensor pixel array active area height - * \sa libcamera::CameraSensorInfo::activeAreaSize - * - * \var ipa_sensor_info::active_area - * \brief The camera sensor pixel array active size - * \sa libcamera::CameraSensorInfo::activeAreaSize - * - * \var ipa_sensor_info::analog_crop.left - * \brief The left coordinate of the analog crop rectangle, relative to the - * pixel array active area - * \sa libcamera::CameraSensorInfo::analogCrop - * - * \var ipa_sensor_info::analog_crop.top - * \brief The top coordinate of the analog crop rectangle, relative to the pixel - * array active area - * \sa libcamera::CameraSensorInfo::analogCrop - * - * \var ipa_sensor_info::analog_crop.width - * \brief The horizontal size of the analog crop rectangle - * \sa libcamera::CameraSensorInfo::analogCrop - * - * \var ipa_sensor_info::analog_crop.height - * \brief The vertical size of the analog crop rectangle - * \sa libcamera::CameraSensorInfo::analogCrop - * - * \var ipa_sensor_info::analog_crop - * \brief The analog crop rectangle - * \sa libcamera::CameraSensorInfo::analogCrop - * - * \var ipa_sensor_info::output_size.width - * \brief The horizontal size of the output image - * \sa libcamera::CameraSensorInfo::outputSize - * - * \var ipa_sensor_info::output_size.height - * \brief The vertical size of the output image - * \sa libcamera::CameraSensorInfo::outputSize - * - * \var ipa_sensor_info::output_size - * \brief The size of the output image - * \sa libcamera::CameraSensorInfo::outputSize - * - * \var ipa_sensor_info::pixel_rate - * \brief The number of pixel produced in a second - * \sa libcamera::CameraSensorInfo::pixelRate - * - * \var ipa_sensor_info::line_length - * \brief The full line length, including blanking, in pixel units - * \sa libcamera::CameraSensorInfo::lineLength - */ - -/** - * \struct ipa_stream - * \brief Stream information for the IPA context operations - * - * \var ipa_stream::id - * \brief Identifier for the stream, defined by the IPA protocol - * - * \var ipa_stream::pixel_format - * \brief The stream pixel format, as defined by the PixelFormat class - * - * \var ipa_stream::width - * \brief The stream width in pixels - * - * \var ipa_stream::height - * \brief The stream height in pixels - */ - -/** - * \struct ipa_control_info_map - * \brief ControlInfoMap description for the IPA context operations - * - * \var ipa_control_info_map::id - * \brief Identifier for the ControlInfoMap, defined by the IPA protocol - * - * \var ipa_control_info_map::data - * \brief Pointer to a control packet for the ControlInfoMap - * \sa ipa_controls.h - * - * \var ipa_control_info_map::size - * \brief The size of the control packet in bytes - */ - -/** - * \struct ipa_buffer_plane - * \brief A plane for an ipa_buffer - * - * \var ipa_buffer_plane::dmabuf - * \brief The dmabuf file descriptor for the plane (-1 for unused planes) - * - * \var ipa_buffer_plane::length - * \brief The plane length in bytes (0 for unused planes) - */ - -/** - * \struct ipa_buffer - * \brief Buffer information for the IPA context operations - * - * \var ipa_buffer::id - * \brief The buffer unique ID (see \ref libcamera::IPABuffer::id) - * - * \var ipa_buffer::num_planes - * \brief The number of used planes in the ipa_buffer::planes array - * - * \var ipa_buffer::planes - * \brief The buffer planes (up to 3) - */ - -/** - * \struct ipa_control_list - * \brief ControlList description for the IPA context operations - * - * \var ipa_control_list::data - * \brief Pointer to a control packet for the ControlList - * \sa ipa_controls.h - * - * \var ipa_control_list::size - * \brief The size of the control packet in bytes - */ - -/** - * \struct ipa_operation_data - * \brief IPA operation data for the IPA context operations - * \sa libcamera::IPAOperationData - * - * \var ipa_operation_data::operation - * \brief IPA protocol operation - * - * \var ipa_operation_data::data - * \brief Pointer to the operation data array - * - * \var ipa_operation_data::num_data - * \brief Number of entries in the ipa_operation_data::data array - * - * \var ipa_operation_data::lists - * \brief Pointer to an array of ipa_control_list - * - * \var ipa_operation_data::num_lists - * \brief Number of entries in the ipa_control_list array - */ - -/** - * \struct ipa_callback_ops - * \brief IPA context operations as a set of function pointers - */ - -/** - * \var ipa_callback_ops::queue_frame_action - * \brief Queue an action associated with a frame to the pipeline handler - * \param[in] cb_ctx The callback context registered with - * ipa_context_ops::register_callbacks - * \param[in] frame The frame number - * - * \sa libcamera::IPAInterface::queueFrameAction - */ - -/** - * \struct ipa_context_ops - * \brief IPA context operations as a set of function pointers - * - * To allow for isolation of IPA modules in separate processes, the functions - * defined in the ipa_context_ops structure return only data related to the - * libcamera side of the operations. In particular, error related to the - * libcamera side of the IPC may be returned. Data returned by the IPA, - * including status information, shall be provided through callbacks from the - * IPA to libcamera. - */ - -/** - * \var ipa_context_ops::destroy - * \brief Destroy the IPA context created by the module's ipaCreate() function - * \param[in] ctx The IPA context - */ - -/** - * \var ipa_context_ops::get_interface - * \brief Retrieve the IPAInterface implemented by the ipa_context (optional) - * \param[in] ctx The IPA context - * - * IPA modules may implement this function to expose their internal - * IPAInterface, if any. When implemented, libcamera may at its sole discretion - * call it and then bypass the ipa_context_ops API by calling the IPAInterface - * methods directly. IPA modules shall still implement and support the full - * ipa_context_ops API. - */ - -/** - * \var ipa_context_ops::init - * \brief Initialise the IPA context - * \param[in] ctx The IPA context - * \param[in] settings The IPA initialization settings - * - * \sa libcamera::IPAInterface::init() - */ - -/** - * \var ipa_context_ops::start - * \brief Start the IPA context - * - * \sa libcamera::IPAInterface::start() - */ - -/** - * \var ipa_context_ops::stop - * \brief Stop the IPA context - * - * \sa libcamera::IPAInterface::stop() - */ - -/** - * \var ipa_context_ops::register_callbacks - * \brief Register callback operation from the IPA to the pipeline handler - * \param[in] ctx The IPA context - * \param[in] callback The IPA callback operations - * \param[in] cb_ctx The callback context, passed to all callback operations - */ - -/** - * \var ipa_context_ops::configure - * \brief Configure the IPA stream and sensor settings - * \param[in] ctx The IPA context - * \param[in] sensor_info Camera sensor information - * \param[in] streams Configuration of all active streams - * \param[in] num_streams The number of entries in the \a streams array - * \param[in] maps Controls provided by the pipeline entities - * \param[in] num_maps The number of entries in the \a maps array - * - * \sa libcamera::IPAInterface::configure() - */ - -/** - * \var ipa_context_ops::map_buffers - * \brief Map buffers shared between the pipeline handler and the IPA - * \param[in] ctx The IPA context - * \param[in] buffers The buffers to map - * \param[in] num_buffers The number of entries in the \a buffers array - * - * The dmabuf file descriptors provided in \a buffers are borrowed from the - * caller and are only guaranteed to be valid during the map_buffers() call. - * Should the callee need to store a copy of the file descriptors, it shall - * duplicate them first with ::%dup(). - * - * \sa libcamera::IPAInterface::mapBuffers() - */ - -/** - * \var ipa_context_ops::unmap_buffers - * \brief Unmap buffers shared by the pipeline to the IPA - * \param[in] ctx The IPA context - * \param[in] ids The IDs of the buffers to unmap - * \param[in] num_buffers The number of entries in the \a ids array - * - * \sa libcamera::IPAInterface::unmapBuffers() - */ - -/** - * \var ipa_context_ops::process_event - * \brief Process an event from the pipeline handler - * \param[in] ctx The IPA context - * - * \sa libcamera::IPAInterface::processEvent() + * be costly. For that reason, functions that cannot afford the high cost + * should be marked as [async] in the mojom file, and they will operate + * asynchronously. This implies that these methods don't return a status, and + * that all methods may copy their arguments. Synchronous functions are still + * allowed, but should be used with caution. */ /** @@ -387,9 +67,10 @@ * \brief Entry point to the IPA modules * * This function is the entry point to the IPA modules. It is implemented by - * every IPA module, and called by libcamera to create a new IPA context. + * every IPA module, and called by libcamera to create a new IPA interface + * instance. * - * \return A newly created IPA context + * \return A newly created IPA interface instance */ namespace libcamera { @@ -499,16 +180,10 @@ namespace libcamera { * \class IPAInterface * \brief C++ Interface for IPA implementation * - * This pure virtual class defines a C++ API corresponding to the ipa_context, - * ipa_context_ops and ipa_callback_ops API. It is used by pipeline handlers to - * interact with IPA modules, and may be used internally in IPA modules if - * desired to benefit from the data and helper classes provided by libcamera. - * - * Functions defined in the ipa_context_ops structure are mapped to IPAInterface - * methods, while functions defined in the ipa_callback_ops are mapped to - * IPAInterface signals. As with the C API, the IPA C++ interface uses - * serializable data types only. It reuses structures defined by the C API, or - * defines corresponding classes using C++ containers when required. + * 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 @@ -516,140 +191,15 @@ namespace libcamera { * mirror core libcamera structures when the latter are not suitable, such as * IPAStream to carry StreamConfiguration data. * - * As for the functions defined in struct ipa_context_ops, the methods defined - * by this class shall not return data from the IPA. + * 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. */ -/** - * \fn IPAInterface::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 IPAInterface::start() - * \brief Start the IPA - * - * This method informs the IPA module that the camera is about to be started. - * The IPA module shall prepare any resources it needs to operate. - * - * \return 0 on success or a negative error code otherwise - */ - -/** - * \fn IPAInterface::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(). - */ - -/** - * \fn IPAInterface::configure() - * \brief Configure the IPA stream and sensor settings - * \param[in] sensorInfo Camera sensor information - * \param[in] streamConfig Configuration of all active streams - * \param[in] entityControls Controls provided by the pipeline entities - * \param[in] ipaConfig Pipeline-handler-specific configuration data - * \param[out] result Pipeline-handler-specific configuration result - * - * This method shall be called when the camera is started to inform the IPA of - * the camera's streams and the sensor settings. The meaning of the numerical - * keys in the \a streamConfig and \a entityControls maps is defined by the IPA - * protocol. - * - * The \a sensorInfo conveys information about the camera sensor settings that - * the pipeline handler has selected for the configuration. The IPA may use - * that information to tune its algorithms. - * - * The \a ipaConfig and \a result parameters carry custom data passed by the - * pipeline handler to the IPA and back. The pipeline handler may set the \a - * result parameter to null if the IPA protocol doesn't need to pass a result - * back through the configure() function. - */ - -/** - * \fn IPAInterface::mapBuffers() - * \brief Map buffers shared between the pipeline handler and the IPA - * \param[in] buffers List of buffers to map - * - * This method informs the IPA module of memory buffers set up by the pipeline - * handler that the IPA needs to access. It provides dmabuf file handles for - * each buffer, and associates the buffers with unique numerical IDs. - * - * IPAs shall map the dmabuf file handles to their address space and keep a - * cache of the mappings, indexed by the buffer numerical IDs. The IDs are used - * in all other IPA interface methods to refer to buffers, including the - * unmapBuffers() method. - * - * All buffers that the pipeline handler wishes to share with an IPA shall be - * mapped with this method. Buffers may be mapped all at once with a single - * call, or mapped and unmapped dynamically at runtime, depending on the IPA - * protocol. Regardless of the protocol, all buffers mapped at a given time - * shall have unique numerical IDs. - * - * The numerical IDs have no meaning defined by the IPA interface, and IPA - * protocols shall not give them any specific meaning either. They should be - * treated as opaque handles by IPAs, with the only exception that ID zero is - * invalid. - * - * \sa unmapBuffers() - * - * \todo Provide a generic implementation of mapBuffers and unmapBuffers for - * IPAs - */ - -/** - * \fn IPAInterface::unmapBuffers() - * \brief Unmap buffers shared by the pipeline to the IPA - * \param[in] ids List of buffer IDs to unmap - * - * This method removes mappings set up with mapBuffers(). Buffers may be - * unmapped all at once with a single call, or selectively at runtime, depending - * on the IPA protocol. Numerical IDs of unmapped buffers may be reused when - * mapping new buffers. - * - * \sa mapBuffers() - */ - -/** - * \fn IPAInterface::processEvent() - * \brief Process an event from the pipeline handler - * \param[in] data IPA operation data - * - * This operation is used by pipeline handlers to inform the IPA module of - * events that occurred during the on-going capture operation. - * - * The event notified by the pipeline handler with this method is handled by the - * IPA, which interprets the operation parameters according to the separately - * documented IPA protocol. - */ - -/** - * \var IPAInterface::queueFrameAction - * \brief Queue an action associated with a frame to the pipeline handler - * \param[in] frame The frame number for the action - * \param[in] data IPA operation data - * - * This signal is emitted when the IPA wishes to queue a FrameAction on the - * pipeline. The pipeline is still responsible for the scheduling of the action - * on its timeline. - * - * This signal is emitted by the IPA to queue an action to be executed by the - * pipeline handler on a frame. The type of action is identified by the - * \a data.operation field, as defined by the IPA protocol, and the rest of the - * \a data is interpreted accordingly. The pipeline handler shall queue the - * action and execute it as appropriate. - * - * The signal is only emitted when the IPA is running, that is after start() and - * before stop() have been called. - */ +/* \todo Figure out how to generate IPAInterface documentation. */ } /* namespace libcamera */ diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp index de512a7f..f53e529b 100644 --- a/src/libcamera/ipa_module.cpp +++ b/src/libcamera/ipa_module.cpp @@ -391,13 +391,13 @@ const std::string &IPAModule::path() const /** * \brief Load the IPA implementation factory from the shared object * - * The IPA module shared object implements an ipa_context object to be used + * The IPA module shared object implements an IPAInterface object to be used * by pipeline handlers. This method loads the factory function from the - * shared object. Later, createContext() can be called to instantiate the - * ipa_context. + * shared object. Later, createInterface() can be called to instantiate the + * IPAInterface. * * This method only needs to be called successfully once, after which - * createContext() can be called as many times as ipa_context instances are + * createInterface() can be called as many times as IPAInterface instances are * needed. * * Calling this function on an invalid module (as returned by isValid()) is @@ -439,20 +439,18 @@ bool IPAModule::load() } /** - * \brief Instantiate an IPA context + * \brief Instantiate an IPA interface * * After loading the IPA module with load(), this method creates an instance of - * the IPA module context. Ownership of the context is passed to the caller, and - * the context shall be destroyed by calling the \ref ipa_context_ops::destroy - * "ipa_context::ops::destroy()" function. + * the IPA module interface. * * Calling this function on a module that has not yet been loaded, or an * invalid module (as returned by load() and isValid(), respectively) is * an error. * - * \return The IPA context on success, or nullptr on error + * \return The IPA interface on success, or nullptr on error */ -struct ipa_context *IPAModule::createContext() +IPAInterface *IPAModule::createInterface() { if (!valid_ || !loaded_) return nullptr; diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index f7f3627c..1472a41a 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -23,7 +23,6 @@ libcamera_sources = files([ 'formats.cpp', 'framebuffer_allocator.cpp', 'geometry.cpp', - 'ipa_context_wrapper.cpp', 'ipa_controls.cpp', 'ipa_data_serializer.cpp', 'ipa_interface.cpp', From patchwork Sat Dec 5 10:30:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10575 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 DED59BDB20 for ; Sat, 5 Dec 2020 10:31:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ABC3E635F2; Sat, 5 Dec 2020 11:31:45 +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="oLUGjRMk"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1BDBD635FA for ; Sat, 5 Dec 2020 11:31:44 +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 B17BA99A; Sat, 5 Dec 2020 11:31:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164303; bh=hVmxGyc2iCpB1q+OS33yMMCDioKxJLOPvL8lfr7rLUg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oLUGjRMk4N43X7taUeMvx3mdIjA18hhj96xfVJg+vuMOb/qhnT9uNIOFYYOspSyIG K+vkpVAKrAI2q/R9CSsIRWl6WaWEoo34iAfYt8HbWcpwl/INBilMfxI8nyfXRATCOE cMdwDxSvGp0gGKShWw9y4MMJriW/0PfJjnE+14sY= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:55 +0900 Message-Id: <20201205103106.242080-13-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 12/23] libcamera: IPAProxy: Remove stop() override 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" Since stop() is part of the IPA interface, and the IPA interface is now generated based on the data definition file per pipeline, this no longer needs to be overrided by the base IPAProxy. Remove it. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- No change in v5 No change in v4 No change in v3 Changes in v2: - remove documentation --- include/libcamera/internal/ipa_proxy.h | 2 -- src/libcamera/ipa_proxy.cpp | 10 ---------- 2 files changed, 12 deletions(-) diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h index 49399f4e..59a5b841 100644 --- a/include/libcamera/internal/ipa_proxy.h +++ b/include/libcamera/internal/ipa_proxy.h @@ -27,8 +27,6 @@ public: std::string configurationFile(const std::string &file) const; - void stop() override = 0; - protected: std::string resolvePath(const std::string &file) const; diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp index ff4d7fd1..23be24ad 100644 --- a/src/libcamera/ipa_proxy.cpp +++ b/src/libcamera/ipa_proxy.cpp @@ -145,16 +145,6 @@ std::string IPAProxy::configurationFile(const std::string &name) const return std::string(); } -/** - * \fn IPAProxy::stop() - * \brief Stop the IPA proxy - * - * This function stops the IPA and releases all the resources acquired by the - * proxy in start(). Calling stop() when the IPA proxy hasn't been started or - * has already been stopped is valid, the proxy shall treat this as a no-op and - * shall not forward the call to the IPA. - */ - /** * \brief Find a valid full path for a proxy worker for a given executable name * \param[in] file File name of proxy worker executable From patchwork Sat Dec 5 10:30:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10576 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 3C4FEBDB20 for ; Sat, 5 Dec 2020 10:31:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 08C3F6360F; Sat, 5 Dec 2020 11:31: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="n/C8EgtO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C2D11635F0 for ; Sat, 5 Dec 2020 11:31:46 +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 6E68E2A4; Sat, 5 Dec 2020 11:31:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164306; bh=EtNH7PZMfA/JHREhAkHthp1FSY+sCW7FCCBbIK+/2u0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n/C8EgtOv15ma0Gk+ilLB40KBmSCPAGA/W/JBHX3nIdP2G7oQOn/GLRyKK1KARhyP vyKtXrxZjnj06zYUGFldfgStFlMTOFhVRAKK0JNVxWRL7VKRykT2elm0s9iiLUhAt/ QMk/1EnqYZKVCB7FqIcvVaIqasK6njaP486oK2TQ= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:56 +0900 Message-Id: <20201205103106.242080-14-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 13/23] libcamera: IPAProxy, IPAManager: Switch to one-proxy-per-pipeline scheme 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" IPAProxy is changed in two major ways: - Every pipeline has its own proxy, to support each pipeline's IPA interface - IPAProxy implementations always encapsulate IPA modules, and switch internally for isolation or threaded The IPAProxy registration mechanism is removed, as each pipeline will have its own proxy, so the pipeline can pass the specialized class name of the IPAProxy to the IPAManager for construction. IPAManager is changed accordingly to support these changes: - createIPA is a template function that takes an IPAProxy class, and always returns an IPAProxy - IPAManager no longer decides on isolation, and simply creates an IPAProxy instance while passing the isolation flag Consequently, the old IPAProxy classes (IPAProxyThread and IPAProxyLinux) are removed. The IPAInterfaceTest is updated to use the new IPAManager interface, and to construct a ProcessManager as no single global instance is created anymore. Signed-off-by: Paul Elder --- Squashed in v5 Changes in v4 - rename libcamera_generated_headers to libcamera_generated_ipa_headers Changes in v3: - declare ProcessManager - add libcamera_generated_headers as dependency to meson - otherwise test might build before the generated IPA headers and #include will fail Changes in v2: - document isolate argument - remove documentation --- This is a combination of 7 commits: --- libcamera: IPAProxy: Add isolate parameter to create() Since IPAProxy implementations now always encapsulate IPA modules, add a parameter to create() to signal if the proxy should isolate the IPA or not. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: IPAManager: Fetch IPAProxy corresponding to pipeline Now that each pipeline handler has its own IPAProxy implementation, make the IPAManager fetch the IPAProxy based on the pipeline handler name. Also, since the IPAProxy is used regardless of isolation or no isolation, remove the isolation check from the proxy selection. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi --- libcamera: IPAManager: add isolation flag to proxy creation When the IPA proxy is created, it needs to know whether to isolate or not. Feed the flag at creation of the IPA proxy. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: IPAManager: Make createIPA return proxy directly Since every pipeline knows the type of the proxy that it needs, and since all IPAs are to be wrapped in a proxy, IPAManager no longer needs to search in the factory list to fetch the proxy factory to construct a factory. Instead, we define createIPA as a template function, and the pipeline handler can declare the proxy type when it calls createIPA. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: IPAProxy: Remove registration mechanism Implementations of IPA proxies use a registration mechanism to register themselves with the main IPA proxy factory. This registration declares static objects, causing a risk of things being constructed before the proper libcamera facilities are ready. Since each pipeline handler has its own IPA proxy and knows the type, it isn't necessary to have a proxy factory. Remove it to alleviate the risk of early construction. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- libcamera: proxy: Remove IPAProxyLinux and IPAProxyThread We have now changed the proxy from per-IPC mechanism to per-pipeline. The per-IPC mechanism proxies are thus no longer needed; remove them. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- tests: ipa_interface_test: Update to use new createIPA Update the IPA interface test to use the new createIPA function from IPAManager. Also create an instance of ProcessManager, as no single global instance is created automatically anymore. Update meson.build to to depend on the generated IPA interface headers. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Documentation/Doxyfile.in | 1 - include/libcamera/internal/ipa_manager.h | 31 +++- include/libcamera/internal/ipa_proxy.h | 29 --- src/libcamera/ipa_manager.cpp | 47 +---- src/libcamera/ipa_proxy.cpp | 91 --------- src/libcamera/proxy/ipa_proxy_linux.cpp | 103 ----------- src/libcamera/proxy/ipa_proxy_thread.cpp | 172 ------------------ src/libcamera/proxy/meson.build | 5 - .../proxy/worker/ipa_proxy_linux_worker.cpp | 90 --------- src/libcamera/proxy/worker/meson.build | 4 - test/ipa/ipa_interface_test.cpp | 8 +- test/ipa/meson.build | 2 +- 12 files changed, 36 insertions(+), 547 deletions(-) delete mode 100644 src/libcamera/proxy/ipa_proxy_linux.cpp delete mode 100644 src/libcamera/proxy/ipa_proxy_thread.cpp delete mode 100644 src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in index 36a0cef3..9126a11d 100644 --- a/Documentation/Doxyfile.in +++ b/Documentation/Doxyfile.in @@ -842,7 +842,6 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/span.h \ @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 \ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ @TOP_BUILDDIR@/include/libcamera/ipa/ \ diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index 4a143b6a..e904a2be 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -14,20 +14,45 @@ #include #include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/log.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/pub_key.h" namespace libcamera { +LOG_DECLARE_CATEGORY(IPAManager) + class IPAManager { public: IPAManager(); ~IPAManager(); - static std::unique_ptr createIPA(PipelineHandler *pipe, - uint32_t maxVersion, - uint32_t minVersion); + template + static std::unique_ptr createIPA(PipelineHandler *pipe, + uint32_t maxVersion, + uint32_t minVersion) + { + IPAModule *m = nullptr; + + for (IPAModule *module : self_->modules_) { + if (module->match(pipe, minVersion, maxVersion)) { + m = module; + break; + } + } + + if (!m) + return nullptr; + + std::unique_ptr proxy = std::make_unique(m, !self_->isSignatureValid(m)); + if (!proxy->isValid()) { + LOG(IPAManager, Error) << "Failed to load proxy"; + return nullptr; + } + + return proxy; + } private: static IPAManager *self_; diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h index 59a5b841..f651a3ae 100644 --- a/include/libcamera/internal/ipa_proxy.h +++ b/include/libcamera/internal/ipa_proxy.h @@ -36,35 +36,6 @@ private: IPAModule *ipam_; }; -class IPAProxyFactory -{ -public: - IPAProxyFactory(const char *name); - virtual ~IPAProxyFactory() = default; - - virtual std::unique_ptr create(IPAModule *ipam) = 0; - - const std::string &name() const { return name_; } - - static void registerType(IPAProxyFactory *factory); - static std::vector &factories(); - -private: - std::string name_; -}; - -#define REGISTER_IPA_PROXY(proxy) \ -class proxy##Factory final : public IPAProxyFactory \ -{ \ -public: \ - proxy##Factory() : IPAProxyFactory(#proxy) {} \ - std::unique_ptr create(IPAModule *ipam) \ - { \ - return std::make_unique(ipam); \ - } \ -}; \ -static proxy##Factory global_##proxy##Factory; - } /* namespace libcamera */ #endif /* __LIBCAMERA_INTERNAL_IPA_PROXY_H__ */ diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index ad05b9c9..93d02d94 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -245,6 +245,7 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) } /** + * \fn IPAManager::createIPA() * \brief Create an IPA proxy that matches a given pipeline handler * \param[in] pipe The pipeline handler that wants a matching IPA proxy * \param[in] minVersion Minimum acceptable version of IPA module @@ -253,52 +254,6 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) * \return A newly created IPA proxy, or nullptr if no matching IPA module is * found or if the IPA proxy fails to initialize */ -std::unique_ptr IPAManager::createIPA(PipelineHandler *pipe, - uint32_t maxVersion, - uint32_t minVersion) -{ - IPAModule *m = nullptr; - - for (IPAModule *module : self_->modules_) { - if (module->match(pipe, minVersion, maxVersion)) { - m = module; - break; - } - } - - if (!m) - return nullptr; - - /* - * Load and run the IPA module in a thread if it has a valid signature, - * or isolate it in a separate process otherwise. - * - * \todo Implement a better proxy selection - */ - const char *proxyName = self_->isSignatureValid(m) - ? "IPAProxyThread" : "IPAProxyLinux"; - IPAProxyFactory *pf = nullptr; - - for (IPAProxyFactory *factory : IPAProxyFactory::factories()) { - if (!strcmp(factory->name().c_str(), proxyName)) { - pf = factory; - break; - } - } - - if (!pf) { - LOG(IPAManager, Error) << "Failed to get proxy factory"; - return nullptr; - } - - std::unique_ptr proxy = pf->create(m); - if (!proxy->isValid()) { - LOG(IPAManager, Error) << "Failed to load proxy"; - return nullptr; - } - - return proxy; -} bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const { diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp index 23be24ad..29c0e9e0 100644 --- a/src/libcamera/ipa_proxy.cpp +++ b/src/libcamera/ipa_proxy.cpp @@ -30,17 +30,11 @@ LOG_DEFINE_CATEGORY(IPAProxy) * \brief IPA Proxy * * Isolate IPA into separate process. - * - * Every subclass of proxy shall be registered with libcamera using - * the REGISTER_IPA_PROXY() macro. */ /** * \brief Construct an IPAProxy instance * \param[in] ipam The IPA module - * - * IPAProxy instances shall be constructed through the IPAProxyFactory::create() - * method implemented by the respective factories. */ IPAProxy::IPAProxy(IPAModule *ipam) : valid_(false), ipam_(ipam) @@ -219,89 +213,4 @@ std::string IPAProxy::resolvePath(const std::string &file) const * construction. */ -/** - * \class IPAProxyFactory - * \brief Registration of IPAProxy classes and creation of instances - * - * To facilitate discovery and instantiation of IPAProxy classes, the - * IPAProxyFactory class maintains a registry of IPAProxy classes. Each - * IPAProxy subclass shall register itself using the REGISTER_IPA_PROXY() - * macro, which will create a corresponding instance of a IPAProxyFactory - * subclass and register it with the static list of factories. - */ - -/** - * \brief Construct a IPAProxy factory - * \param[in] name Name of the IPAProxy class - * - * Creating an instance of the factory registers is with the global list of - * factories, accessible through the factories() function. - * - * The factory \a name is used for debugging and IPAProxy matching purposes - * and shall be unique. - */ -IPAProxyFactory::IPAProxyFactory(const char *name) - : name_(name) -{ - registerType(this); -} - -/** - * \fn IPAProxyFactory::create() - * \brief Create an instance of the IPAProxy corresponding to the factory - * \param[in] ipam The IPA module - * - * This virtual function is implemented by the REGISTER_IPA_PROXY() macro. - * It creates a IPAProxy instance that isolates an IPA interface designated - * by the IPA module \a ipam. - * - * \return A pointer to a newly constructed instance of the IPAProxy subclass - * corresponding to the factory - */ - -/** - * \fn IPAProxyFactory::name() - * \brief Retrieve the factory name - * \return The factory name - */ - -/** - * \brief Add a IPAProxy class to the registry - * \param[in] factory Factory to use to construct the IPAProxy - * - * The caller is responsible to guarantee the uniqueness of the IPAProxy name. - */ -void IPAProxyFactory::registerType(IPAProxyFactory *factory) -{ - std::vector &factories = IPAProxyFactory::factories(); - - factories.push_back(factory); - - LOG(IPAProxy, Debug) - << "Registered proxy \"" << factory->name() << "\""; -} - -/** - * \brief Retrieve the list of all IPAProxy factories - * - * The static factories map is defined inside the function to ensure it gets - * initialized on first use, without any dependency on link order. - * - * \return The list of pipeline handler factories - */ -std::vector &IPAProxyFactory::factories() -{ - static std::vector factories; - return factories; -} - -/** - * \def REGISTER_IPA_PROXY - * \brief Register a IPAProxy with the IPAProxy factory - * \param[in] proxy Class name of IPAProxy derived class to register - * - * Register a proxy subclass with the factory and make it available to - * isolate IPA modules. - */ - } /* namespace libcamera */ diff --git a/src/libcamera/proxy/ipa_proxy_linux.cpp b/src/libcamera/proxy/ipa_proxy_linux.cpp deleted file mode 100644 index b78a0e45..00000000 --- a/src/libcamera/proxy/ipa_proxy_linux.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_proxy_linux.cpp - Default Image Processing Algorithm proxy for Linux - */ - -#include - -#include -#include - -#include "libcamera/internal/ipa_module.h" -#include "libcamera/internal/ipa_proxy.h" -#include "libcamera/internal/ipc_unixsocket.h" -#include "libcamera/internal/log.h" -#include "libcamera/internal/process.h" - -namespace libcamera { - -LOG_DECLARE_CATEGORY(IPAProxy) - -class IPAProxyLinux : public IPAProxy -{ -public: - IPAProxyLinux(IPAModule *ipam); - ~IPAProxyLinux(); - - int init([[maybe_unused]] const IPASettings &settings) override - { - return 0; - } - int start() override { return 0; } - void stop() override {} - void configure([[maybe_unused]] const CameraSensorInfo &sensorInfo, - [[maybe_unused]] const std::map &streamConfig, - [[maybe_unused]] const std::map &entityControls, - [[maybe_unused]] const IPAOperationData &ipaConfig, - [[maybe_unused]] IPAOperationData *result) override {} - void mapBuffers([[maybe_unused]] const std::vector &buffers) override {} - void unmapBuffers([[maybe_unused]] const std::vector &ids) override {} - void processEvent([[maybe_unused]] const IPAOperationData &event) override {} - -private: - void readyRead(IPCUnixSocket *ipc); - - Process *proc_; - - IPCUnixSocket *socket_; -}; - -IPAProxyLinux::IPAProxyLinux(IPAModule *ipam) - : IPAProxy(ipam), proc_(nullptr), socket_(nullptr) -{ - LOG(IPAProxy, Debug) - << "initializing dummy proxy: loading IPA from " - << ipam->path(); - - std::vector fds; - std::vector args; - args.push_back(ipam->path()); - const std::string path = resolvePath("ipa_proxy_linux"); - if (path.empty()) { - LOG(IPAProxy, Error) - << "Failed to get proxy worker path"; - return; - } - - socket_ = new IPCUnixSocket(); - int fd = socket_->create(); - if (fd < 0) { - LOG(IPAProxy, Error) - << "Failed to create socket"; - return; - } - socket_->readyRead.connect(this, &IPAProxyLinux::readyRead); - args.push_back(std::to_string(fd)); - fds.push_back(fd); - - proc_ = new Process(); - int ret = proc_->start(path, args, fds); - if (ret) { - LOG(IPAProxy, Error) - << "Failed to start proxy worker process"; - return; - } - - valid_ = true; -} - -IPAProxyLinux::~IPAProxyLinux() -{ - delete proc_; - delete socket_; -} - -void IPAProxyLinux::readyRead([[maybe_unused]] IPCUnixSocket *ipc) -{ -} - -REGISTER_IPA_PROXY(IPAProxyLinux) - -} /* namespace libcamera */ diff --git a/src/libcamera/proxy/ipa_proxy_thread.cpp b/src/libcamera/proxy/ipa_proxy_thread.cpp deleted file mode 100644 index eead2883..00000000 --- a/src/libcamera/proxy/ipa_proxy_thread.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2020, Google Inc. - * - * ipa_proxy_thread.cpp - Proxy running an Image Processing Algorithm in a thread - */ - -#include - -#include -#include - -#include "libcamera/internal/ipa_context_wrapper.h" -#include "libcamera/internal/ipa_module.h" -#include "libcamera/internal/ipa_proxy.h" -#include "libcamera/internal/log.h" -#include "libcamera/internal/thread.h" - -namespace libcamera { - -LOG_DECLARE_CATEGORY(IPAProxy) - -class IPAProxyThread : public IPAProxy, public Object -{ -public: - IPAProxyThread(IPAModule *ipam); - - int init(const IPASettings &settings) override; - int start() override; - void stop() override; - - void configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) override; - void mapBuffers(const std::vector &buffers) override; - void unmapBuffers(const std::vector &ids) override; - void processEvent(const IPAOperationData &event) override; - -private: - void queueFrameAction(unsigned int frame, const IPAOperationData &data); - - /* Helper class to invoke processEvent() in another thread. */ - class ThreadProxy : public Object - { - public: - void setIPA(IPAInterface *ipa) - { - ipa_ = ipa; - } - - int start() - { - return ipa_->start(); - } - - void stop() - { - ipa_->stop(); - } - - void processEvent(const IPAOperationData &event) - { - ipa_->processEvent(event); - } - - private: - IPAInterface *ipa_; - }; - - bool running_; - Thread thread_; - ThreadProxy proxy_; - std::unique_ptr ipa_; -}; - -IPAProxyThread::IPAProxyThread(IPAModule *ipam) - : IPAProxy(ipam), running_(false) -{ - if (!ipam->load()) - return; - - struct ipa_context *ctx = ipam->createContext(); - if (!ctx) { - LOG(IPAProxy, Error) - << "Failed to create IPA context for " << ipam->path(); - return; - } - - ipa_ = std::make_unique(ctx); - proxy_.setIPA(ipa_.get()); - - /* - * Proxy the queueFrameAction signal to dispatch it in the caller's - * thread. - */ - ipa_->queueFrameAction.connect(this, &IPAProxyThread::queueFrameAction); - - valid_ = true; -} - -int IPAProxyThread::init(const IPASettings &settings) -{ - int ret = ipa_->init(settings); - if (ret) - return ret; - - proxy_.moveToThread(&thread_); - - return 0; -} - -int IPAProxyThread::start() -{ - running_ = true; - thread_.start(); - - return proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking); -} - -void IPAProxyThread::stop() -{ - if (!running_) - return; - - running_ = false; - - proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking); - - thread_.exit(); - thread_.wait(); -} - -void IPAProxyThread::configure(const CameraSensorInfo &sensorInfo, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) -{ - ipa_->configure(sensorInfo, streamConfig, entityControls, ipaConfig, - result); -} - -void IPAProxyThread::mapBuffers(const std::vector &buffers) -{ - ipa_->mapBuffers(buffers); -} - -void IPAProxyThread::unmapBuffers(const std::vector &ids) -{ - ipa_->unmapBuffers(ids); -} - -void IPAProxyThread::processEvent(const IPAOperationData &event) -{ - if (!running_) - return; - - /* Dispatch the processEvent() call to the thread. */ - proxy_.invokeMethod(&ThreadProxy::processEvent, ConnectionTypeQueued, - event); -} - -void IPAProxyThread::queueFrameAction(unsigned int frame, const IPAOperationData &data) -{ - IPAInterface::queueFrameAction.emit(frame, data); -} - -REGISTER_IPA_PROXY(IPAProxyThread) - -} /* namespace libcamera */ diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build index ac68ad08..5965589f 100644 --- a/src/libcamera/proxy/meson.build +++ b/src/libcamera/proxy/meson.build @@ -1,10 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ - 'ipa_proxy_linux.cpp', - 'ipa_proxy_thread.cpp', -]) - # generate ipa_proxy_{pipeline}.cpp foreach mojom : ipa_mojoms proxy = custom_target(mojom['name'] + '_proxy_cpp', diff --git a/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp b/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp deleted file mode 100644 index bdbac988..00000000 --- a/src/libcamera/proxy/worker/ipa_proxy_linux_worker.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * ipa_proxy_linux_worker.cpp - Default Image Processing Algorithm proxy worker for Linux - */ - -#include -#include -#include - -#include -#include - -#include "libcamera/internal/event_dispatcher.h" -#include "libcamera/internal/ipa_module.h" -#include "libcamera/internal/ipc_unixsocket.h" -#include "libcamera/internal/log.h" -#include "libcamera/internal/thread.h" - -using namespace libcamera; - -LOG_DEFINE_CATEGORY(IPAProxyLinuxWorker) - -void readyRead(IPCUnixSocket *ipc) -{ - IPCUnixSocket::Payload message; - int ret; - - ret = ipc->receive(&message); - if (ret) { - LOG(IPAProxyLinuxWorker, Error) - << "Receive message failed: " << ret; - return; - } - - LOG(IPAProxyLinuxWorker, Debug) << "Received a message!"; -} - -int main(int argc, char **argv) -{ - /* Uncomment this for debugging. */ -#if 0 - std::string logPath = "/tmp/libcamera.worker." + - std::to_string(getpid()) + ".log"; - logSetFile(logPath.c_str()); -#endif - - if (argc < 3) { - LOG(IPAProxyLinuxWorker, Debug) - << "Tried to start worker with no args"; - return EXIT_FAILURE; - } - - int fd = std::stoi(argv[2]); - LOG(IPAProxyLinuxWorker, Debug) - << "Starting worker for IPA module " << argv[1] - << " with IPC fd = " << fd; - - std::unique_ptr ipam = std::make_unique(argv[1]); - if (!ipam->isValid() || !ipam->load()) { - LOG(IPAProxyLinuxWorker, Error) - << "IPAModule " << argv[1] << " should be valid but isn't"; - return EXIT_FAILURE; - } - - IPCUnixSocket socket; - if (socket.bind(fd) < 0) { - LOG(IPAProxyLinuxWorker, Error) << "IPC socket binding failed"; - return EXIT_FAILURE; - } - socket.readyRead.connect(&readyRead); - - struct ipa_context *ipac = ipam->createContext(); - if (!ipac) { - LOG(IPAProxyLinuxWorker, Error) << "Failed to create IPA context"; - return EXIT_FAILURE; - } - - LOG(IPAProxyLinuxWorker, Debug) << "Proxy worker successfully started"; - - /* \todo upgrade listening loop */ - EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); - while (1) - dispatcher->processEvents(); - - ipac->ops->destroy(ipac); - - return 0; -} diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build index 8e0b978a..f3129b76 100644 --- a/src/libcamera/proxy/worker/meson.build +++ b/src/libcamera/proxy/worker/meson.build @@ -1,9 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -ipa_proxy_sources = [ - ['ipa_proxy_linux', 'ipa_proxy_linux_worker.cpp'] -] - proxy_install_dir = join_paths(get_option('libexecdir'), 'libcamera') # generate ipa_proxy_{pipeline}_worker.cpp diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp index 9f575f93..45d322f4 100644 --- a/test/ipa/ipa_interface_test.cpp +++ b/test/ipa/ipa_interface_test.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/event_dispatcher.h" @@ -20,6 +21,7 @@ #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/ipa_module.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/process.h" #include "libcamera/internal/thread.h" #include "libcamera/internal/timer.h" @@ -95,7 +97,7 @@ protected: EventDispatcher *dispatcher = thread()->eventDispatcher(); Timer timer; - ipa_ = IPAManager::createIPA(pipe_.get(), 0, 0); + ipa_ = IPAManager::createIPA(pipe_.get(), 0, 0); if (!ipa_) { cerr << "Failed to create VIMC IPA interface" << endl; return TestFail; @@ -163,8 +165,10 @@ private: } } + ProcessManager processManager_; + std::shared_ptr pipe_; - std::unique_ptr ipa_; + std::unique_ptr ipa_; std::unique_ptr ipaManager_; enum IPAOperationCode trace_; EventNotifier *notifier_; diff --git a/test/ipa/meson.build b/test/ipa/meson.build index e4f0818a..e8a041b5 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -6,7 +6,7 @@ ipa_test = [ ] foreach t : ipa_test - exe = executable(t[0], t[1], + exe = executable(t[0], [t[1], libcamera_generated_ipa_headers], dependencies : libcamera_dep, link_with : [libipa, test_libraries], include_directories : [libipa_includes, test_includes_internal]) From patchwork Sat Dec 5 10:30:57 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10577 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 C36C4BDB20 for ; Sat, 5 Dec 2020 10:31:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F7E0635F7; Sat, 5 Dec 2020 11:31:50 +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="oCF6LaPw"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7438D63601 for ; Sat, 5 Dec 2020 11:31:49 +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 3FC662A4; Sat, 5 Dec 2020 11:31:46 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164309; bh=hGyMP14o0xAWDI1VmCjChvzZx5XQOu0/MUYZHaW7uxo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oCF6LaPwXckZla+Onml2Pjg7h8nr80YiEYFOdUKYGXTQ6Myxyo2iuO3xoMKHyIGwT FX/eHXpnXKqUfnEENC9GdiY63BxHtJZuNetkyildVe/TWbn35F7VLbRKODIabYRDiy syAnrR/EWMhQT0+ufMV3w05ZusPHc4XmT//7Trg4= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:57 +0900 Message-Id: <20201205103106.242080-15-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 14/23] libcamera: PipelineHandler: Remove IPA from base class 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" Since pipeline handlers now have their own IPA interface types, it can no longer be defined in the base class, and each pipeline handler implementation must declare it and its type themselves. Remove it from the base class. Signed-off-by: Paul Elder Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- No change in v5 No change in v4 No change in v3 Changes in v2: - remove documentation --- include/libcamera/internal/pipeline_handler.h | 1 - src/libcamera/pipeline_handler.cpp | 8 -------- 2 files changed, 9 deletions(-) diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index c12c8904..57ad3052 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -47,7 +47,6 @@ public: std::list queuedRequests_; ControlInfoMap controlInfo_; ControlList properties_; - std::unique_ptr ipa_; private: CameraData(const CameraData &) = delete; diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 894200ee..b893b0e7 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -106,14 +106,6 @@ LOG_DEFINE_CATEGORY(Pipeline) * when creating the camera, and shall not be modified afterwards. */ -/** - * \var CameraData::ipa_ - * \brief The IPA module used by the camera - * - * Reference to the Image Processing Algorithms (IPA) operating on the camera's - * stream(s). If no IPA exists for the camera, this field is set to nullptr. - */ - /** * \class PipelineHandler * \brief Create and manage cameras based on a set of media devices From patchwork Sat Dec 5 10:30:58 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10578 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 1F3FFBDB20 for ; Sat, 5 Dec 2020 10:31:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E0624635F7; Sat, 5 Dec 2020 11:31: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="PCN/H+M5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3BA33635F0 for ; Sat, 5 Dec 2020 11:31: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 E002E2A4; Sat, 5 Dec 2020 11:31:49 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164311; bh=Fm4HJRsgUgglTjCk0tlLbatRl2NUctPVXAw+w0687E4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PCN/H+M53Xd/Fo5RMyEjMToIobW22o2mwkgpkxTkkuL7H64jkES3uNUIRcItEgB+r ak/2Uc2yhDidwSKNGPYIPpV/1U5I4+t1vBtnaniw7493rST5QV6juqm2Ptweml4VBs d/TiEHACZqimNpFX1u8/1B9TnWWCYYI1YjafCXZA= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:58 +0900 Message-Id: <20201205103106.242080-16-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 15/23] 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 --- 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 | 48 ++++++++++++++++++++++++++ include/libcamera/ipa/meson.build | 14 +++++++- src/libcamera/proxy/meson.build | 4 +-- src/libcamera/proxy/worker/meson.build | 4 +-- 4 files changed, 65 insertions(+), 5 deletions(-) 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..95d12b63 --- /dev/null +++ b/include/libcamera/ipa/core.mojom @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Any libcamera objects that are used by any interfaces that aren't defined + * in mojom must be declared here, and a (de)serializer be implemented as a + * template specialization of IPADataSerializer. In addition, the corresponding + * header file (or forward-declarations) must be included in {pipeline_name}.h. + * + * For libcamera types, the [hasFd] attribute is needed to notify the compiler + * that the struct embeds a FileDescriptor. + */ +struct CameraSensorInfo {}; +struct ControlInfoMap {}; +struct ControlList {}; +struct FileDescriptor {}; + +/* \todo Define these structures fully in mojom. */ +[hasFd] struct IPABuffer {}; +struct IPASettings {}; +struct IPAStream {}; + +/** + * \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 start() + * \brief Start the IPA + * + * This method informs the IPA module that the camera is about to be started. + * The IPA module shall prepare any resources it needs to operate. + * + * \return 0 on success or a negative error code otherwise + */ + +/** + * \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(). + */ diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index d7a2b6b2..087f5bbd 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -13,6 +13,17 @@ install_headers(libcamera_ipa_headers, # Prepare IPA/IPC generation components # +core_mojom_file = 'core.mojom' +ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module', + input : core_mojom_file, + output : core_mojom_file + '-module', + command : [ + mojom_parser, + '--output-root', meson.build_root(), + '--input-root', meson.source_root(), + '--mojoms', '@INPUT@' + ]) + ipa_mojom_files = [] ipa_mojoms = [] @@ -30,6 +41,7 @@ foreach file : ipa_mojom_files mojom = custom_target(file.split('.')[0] + '_mojom_module', input : file, output : file + '-module', + depends : ipa_mojom_core, command : [ mojom_parser, '--output-root', meson.build_root(), @@ -68,7 +80,7 @@ foreach file : ipa_mojom_files # ipa_proxy_{pipeline}.h proxy_header = custom_target(name + '_proxy_h', input : mojom, - output : 'ipa_proxy_' + name + '.h', + output : name + '_ipa_proxy.h', depends : mojom_templates, command : [ mojom_generator, 'generate', diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build index 5965589f..a3f6b223 100644 --- a/src/libcamera/proxy/meson.build +++ b/src/libcamera/proxy/meson.build @@ -4,8 +4,8 @@ foreach mojom : ipa_mojoms proxy = custom_target(mojom['name'] + '_proxy_cpp', input : mojom['mojom'], - output : 'ipa_proxy_' + mojom['name'] + '.cpp', - depends : [mojom_templates], + output : mojom['name'] + '_ipa_proxy.cpp', + depends : mojom_templates, command : [ mojom_generator, 'generate', '-g', 'libcamera', diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build index f3129b76..cc55078f 100644 --- a/src/libcamera/proxy/worker/meson.build +++ b/src/libcamera/proxy/worker/meson.build @@ -6,8 +6,8 @@ proxy_install_dir = join_paths(get_option('libexecdir'), 'libcamera') foreach mojom : ipa_mojoms worker = custom_target(mojom['name'] + '_proxy_worker', input : mojom['mojom'], - output : 'ipa_proxy_' + mojom['name'] + '_worker.cpp', - depends : [mojom_templates], + output : mojom['name'] + '_ipa_proxy_worker.cpp', + depends : mojom_templates, command : [ mojom_generator, 'generate', '-g', 'libcamera', From patchwork Sat Dec 5 10:30:59 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10579 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 713E2BDB20 for ; Sat, 5 Dec 2020 10:31:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3C6EB6360A; Sat, 5 Dec 2020 11:31:54 +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="tf7PL/Wy"; 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 485D36360A for ; Sat, 5 Dec 2020 11:31: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 AF37A2A4; Sat, 5 Dec 2020 11:31:51 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164312; bh=jkMmEfmBZu75bYo9vyv++4LTDqQ7RFD8v5WM+5tUKO4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tf7PL/Wyoam7SiXAg1DjG8D+Md5w1+NXJuRIqL5NvTgYaMMj0xaCRDT80w/PG+z1A v5+2/RzKCtot4xCRFS1Vbfi9mRU7gyn+G+y2jRgOXCi0Ds4ceIJ/Td4rzlbIrVUelm rlwG7DOubvdcAXsHt7ZrzogyfGXweUubcAr3s8g0= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:30:59 +0900 Message-Id: <20201205103106.242080-17-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 16/23] ipa: raspberrypi: Add mojom data definition file 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 mojom data definition for raspberrypi pipeline handler's IPAs. This is a direct translation of what the raspberrypi pipeline handler and IPA was using before with IPAOperationData. Also move the enums from raspberrypi.h to raspberrypi.mojom Signed-off-by: Paul Elder --- Changes in v5: - rename some structs Changes in v4: - rename IPARPiCallbackInterface to IPARPiEventInterface Changes in v3: - remove stray comment about how our compiler will generate code - add ipa.rpi namespace - not ipa.RPi, because namespace conflict - remove RPi prefix from struct and enum names - add transform parameter to struct ConfigInput Changes in v2: - rebased on "libcamera: pipeline: ipa: raspberrypi: Rework drop frame signalling" - add license - move generic documentation to core.mojom - move documentation from IPA interface - customize the RPi IPA interface to make the pipeline and IPA code cleaner --- include/libcamera/ipa/meson.build | 4 +- include/libcamera/ipa/raspberrypi.h | 18 ---- include/libcamera/ipa/raspberrypi.mojom | 119 ++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 include/libcamera/ipa/raspberrypi.mojom diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index 087f5bbd..5c838d5c 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -24,7 +24,9 @@ ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module', '--mojoms', '@INPUT@' ]) -ipa_mojom_files = [] +ipa_mojom_files = [ + 'raspberrypi.mojom', +] ipa_mojoms = [] diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h index 2b55dbfc..df30b4a2 100644 --- a/include/libcamera/ipa/raspberrypi.h +++ b/include/libcamera/ipa/raspberrypi.h @@ -16,24 +16,6 @@ namespace libcamera { namespace RPi { -enum ConfigParameters { - IPA_CONFIG_LS_TABLE = (1 << 0), - IPA_CONFIG_STAGGERED_WRITE = (1 << 1), - IPA_CONFIG_SENSOR = (1 << 2), - IPA_CONFIG_DROP_FRAMES = (1 << 3), -}; - -enum Operations { - IPA_ACTION_V4L2_SET_STAGGERED = 1, - IPA_ACTION_V4L2_SET_ISP, - IPA_ACTION_STATS_METADATA_COMPLETE, - IPA_ACTION_RUN_ISP, - IPA_ACTION_EMBEDDED_COMPLETE, - IPA_EVENT_SIGNAL_STAT_READY, - IPA_EVENT_SIGNAL_ISP_PREPARE, - IPA_EVENT_QUEUE_REQUEST, -}; - enum BufferMask { ID = 0x00ffff, STATS = 0x010000, diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom new file mode 100644 index 00000000..81744ac6 --- /dev/null +++ b/include/libcamera/ipa/raspberrypi.mojom @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +module ipa.rpi; + +import "include/libcamera/ipa/core.mojom"; + +enum ConfigParameters { + ConfigLsTable = 0x01, + ConfigStaggeredWrite = 0x02, + ConfigSensor = 0x04, + ConfigDropFrames = 0x08, +}; + +struct SensorConfig { + uint32 gainDelay; + uint32 exposureDelay; + uint32 sensorMetadata; +}; + +struct ISPConfig { + uint32 embeddedbufferId; + uint32 bayerbufferId; +}; + +struct ConfigInput { + uint32 op; + uint32 transform; + FileDescriptor lsTableHandle; + int32 lsTableHandleStatic = -1; +}; + +struct ConfigOutput { + uint32 op; + SensorConfig sensorConfig; + ControlList controls; + int32 dropFrameCount; +}; + +interface IPARPiInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + /** + * \fn configure() + * \brief Configure the IPA stream and sensor settings + * \param[in] sensorInfo Camera sensor information + * \param[in] streamConfig Configuration of all active streams + * \param[in] entityControls Controls provided by the pipeline entities + * \param[in] ipaConfig Pipeline-handler-specific configuration data + * \param[out] result Pipeline-handler-specific configuration result + * + * This method shall be called when the camera is started to inform the IPA of + * the camera's streams and the sensor settings. + * + * The \a sensorInfo conveys information about the camera sensor settings that + * the pipeline handler has selected for the configuration. + * + * The \a ipaConfig and \a result parameters carry data passed by the + * pipeline handler to the IPA and back. + */ + configure(CameraSensorInfo sensorInfo, + map streamConfig, + map entityControls, + ConfigInput ipaConfig) + => (ConfigOutput results); + + /** + * \fn mapBuffers() + * \brief Map buffers shared between the pipeline handler and the IPA + * \param[in] buffers List of buffers to map + * + * This method informs the IPA module of memory buffers set up by the pipeline + * handler that the IPA needs to access. It provides dmabuf file handles for + * each buffer, and associates the buffers with unique numerical IDs. + * + * IPAs shall map the dmabuf file handles to their address space and keep a + * cache of the mappings, indexed by the buffer numerical IDs. The IDs are used + * in all other IPA interface methods to refer to buffers, including the + * unmapBuffers() method. + * + * All buffers that the pipeline handler wishes to share with an IPA shall be + * mapped with this method. Buffers may be mapped all at once with a single + * call, or mapped and unmapped dynamically at runtime, depending on the IPA + * protocol. Regardless of the protocol, all buffers mapped at a given time + * shall have unique numerical IDs. + * + * The numerical IDs have no meaning defined by the IPA interface, and + * should be treated as opaque handles by IPAs, with the only exception + * that ID zero is invalid. + * + * \sa unmapBuffers() + */ + mapBuffers(array buffers); + + /** + * \fn unmapBuffers() + * \brief Unmap buffers shared by the pipeline to the IPA + * \param[in] ids List of buffer IDs to unmap + * + * This method removes mappings set up with mapBuffers(). Numerical IDs + * of unmapped buffers may be reused when mapping new buffers. + * + * \sa mapBuffers() + */ + unmapBuffers(array ids); + + [async] signalStatReady(uint32 bufferId); + [async] signalQueueRequest(ControlList controls); + [async] signalIspPrepare(ISPConfig data); +}; + +interface IPARPiEventInterface { + statsMetadataComplete(uint32 bufferId, ControlList controls); + runIsp(uint32 bufferId); + embeddedComplete(uint32 bufferId); + setIsp(ControlList controls); + setStaggered(ControlList controls); +}; From patchwork Sat Dec 5 10:31:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10580 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 DBCD4BDB20 for ; Sat, 5 Dec 2020 10:31:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A6CED63615; Sat, 5 Dec 2020 11:31: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="Gg79zLgt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 03579635F0 for ; Sat, 5 Dec 2020 11:31: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 AC9A82A4; Sat, 5 Dec 2020 11:31:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164314; bh=qaDeDaQdueNdCoLtO2W55iBEQzIKdvjmIoo0AWSSbRc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Gg79zLgtC8+IzXscz8oncjMPAL68ZebQeP0mqHKcj1F6VSx/JE5td+soxAIA/Zkqr IJjlc7LvZZI7egWk/p5S5hYIQrb0akP2yTfgmdSUwS/+1oZZO2F//FcKAHNcH2xtsZ l3d4OyLkhSO09sRwWKhldH/tYzs/XwQN2Z8ZPaTo= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:00 +0900 Message-Id: <20201205103106.242080-18-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 17/23] libcamera: pipeline, ipa: raspberrypi: Use new data definition 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" Now that we can generate custom functions and data structures with mojo, switch the raspberrypi pipeline handler and IPA to use the custom data structures as defined in the mojom data definition file. Signed-off-by: Paul Elder --- Changes in v5: - minor fixes - use new struct names Changes in v4: - rename Controls to controls - use the renamed header (raspberrypi_ipa_interface.h) Changes in v3: - use ipa::rpi namespace - rebase on the RPi namespace patch series newly merged into master Changes in v2: - rebased on "libcamera: pipeline: ipa: raspberrypi: Rework drop frame signalling" - use newly customized RPi IPA interface --- include/libcamera/ipa/raspberrypi.h | 41 ++-- src/ipa/raspberrypi/raspberrypi.cpp | 158 ++++++-------- .../pipeline/raspberrypi/raspberrypi.cpp | 193 +++++++++--------- 3 files changed, 182 insertions(+), 210 deletions(-) diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h index df30b4a2..e145de3f 100644 --- a/include/libcamera/ipa/raspberrypi.h +++ b/include/libcamera/ipa/raspberrypi.h @@ -28,24 +28,29 @@ enum BufferMask { static constexpr unsigned int MaxLsGridSize = 32 << 10; /* List of controls handled by the Raspberry Pi IPA */ -static const ControlInfoMap Controls = { - { &controls::AeEnable, ControlInfo(false, true) }, - { &controls::ExposureTime, ControlInfo(0, 999999) }, - { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) }, - { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) }, - { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) }, - { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) }, - { &controls::ExposureValue, ControlInfo(0.0f, 16.0f) }, - { &controls::AwbEnable, ControlInfo(false, true) }, - { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, - { &controls::AwbMode, ControlInfo(controls::AwbModeValues) }, - { &controls::Brightness, ControlInfo(-1.0f, 1.0f) }, - { &controls::Contrast, ControlInfo(0.0f, 32.0f) }, - { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, - { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, - { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) }, - { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, -}; +static ControlInfoMap controls; + +inline void initializeRPiControls() +{ + controls = { + { &controls::AeEnable, ControlInfo(false, true) }, + { &controls::ExposureTime, ControlInfo(0, 999999) }, + { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) }, + { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) }, + { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) }, + { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) }, + { &controls::ExposureValue, ControlInfo(0.0f, 16.0f) }, + { &controls::AwbEnable, ControlInfo(false, true) }, + { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, + { &controls::AwbMode, ControlInfo(controls::AwbModeValues) }, + { &controls::Brightness, ControlInfo(-1.0f, 1.0f) }, + { &controls::Contrast, ControlInfo(0.0f, 32.0f) }, + { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, + { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, + { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) }, + { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, + }; +} } /* namespace RPi */ diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp index 4dc02dc8..29e4854e 100644 --- a/src/ipa/raspberrypi/raspberrypi.cpp +++ b/src/ipa/raspberrypi/raspberrypi.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -59,7 +60,7 @@ constexpr unsigned int DefaultExposureTime = 20000; LOG_DEFINE_CATEGORY(IPARPI) -class IPARPi : public IPAInterface +class IPARPi : public ipa::rpi::IPARPiInterface { public: IPARPi() @@ -67,6 +68,7 @@ public: frameCount_(0), checkCount_(0), mistrustCount_(0), lsTable_(nullptr) { + RPi::initializeRPiControls(); } ~IPARPi() @@ -81,12 +83,14 @@ public: void configure(const CameraSensorInfo &sensorInfo, const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &data, - IPAOperationData *response) override; + const std::map &entityControls, + const ipa::rpi::ConfigInput &data, + ipa::rpi::ConfigOutput *response) override; void mapBuffers(const std::vector &buffers) override; void unmapBuffers(const std::vector &ids) override; - void processEvent(const IPAOperationData &event) override; + void signalStatReady(const uint32_t bufferId) override; + void signalQueueRequest(const ControlList &controls) override; + void signalIspPrepare(const ipa::rpi::ISPConfig &data) override; private: void setMode(const CameraSensorInfo &sensorInfo); @@ -142,6 +146,11 @@ private: /* LS table allocation passed in from the pipeline handler. */ FileDescriptor lsTableHandle_; + /* + * LS table allocation passed in from the pipeline handler, + * in the context of the pipeline handler. + */ + int32_t lsTableHandlePH_; void *lsTable_; }; @@ -191,15 +200,13 @@ void IPARPi::setMode(const CameraSensorInfo &sensorInfo) void IPARPi::configure(const CameraSensorInfo &sensorInfo, [[maybe_unused]] const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *result) + const std::map &entityControls, + const ipa::rpi::ConfigInput &ipaConfig, + ipa::rpi::ConfigOutput *result) { if (entityControls.empty()) return; - result->operation = 0; - unicamCtrls_ = entityControls.at(0); ispCtrls_ = entityControls.at(1); @@ -223,32 +230,28 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo, helper_->GetDelays(exposureDelay, gainDelay); sensorMetadata = helper_->SensorEmbeddedDataPresent(); - result->data.push_back(gainDelay); - result->data.push_back(exposureDelay); - result->data.push_back(sensorMetadata); - - result->operation |= RPi::IPA_CONFIG_STAGGERED_WRITE; + result->op_ |= ipa::rpi::ConfigStaggeredWrite; + result->sensorConfig_.gainDelay_ = gainDelay; + result->sensorConfig_.exposureDelay_ = exposureDelay; + result->sensorConfig_.sensorMetadata_ = sensorMetadata; } /* Re-assemble camera mode using the sensor info. */ setMode(sensorInfo); - /* - * The ipaConfig.data always gives us the user transform first. Note that - * this will always make the LS table pointer (if present) element 1. - */ - mode_.transform = static_cast(ipaConfig.data[0]); + mode_.transform = static_cast(ipaConfig.transform_); /* Store the lens shading table pointer and handle if available. */ - if (ipaConfig.operation & RPi::IPA_CONFIG_LS_TABLE) { + if (ipaConfig.op_ & ipa::rpi::ConfigLsTable) { /* Remove any previous table, if there was one. */ if (lsTable_) { munmap(lsTable_, RPi::MaxLsGridSize); lsTable_ = nullptr; } - /* Map the LS table buffer into user space (now element 1). */ - lsTableHandle_ = FileDescriptor(ipaConfig.data[1]); + /* Map the LS table buffer into user space. */ + lsTableHandle_ = FileDescriptor(ipaConfig.lsTableHandle_); + lsTableHandlePH_ = ipaConfig.lsTableHandleStatic_; if (lsTableHandle_.isValid()) { lsTable_ = mmap(nullptr, RPi::MaxLsGridSize, PROT_READ | PROT_WRITE, MAP_SHARED, lsTableHandle_.fd(), 0); @@ -270,18 +273,15 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo, */ frameCount_ = 0; checkCount_ = 0; - unsigned int dropFrame = 0; + result->op_ |= ipa::rpi::ConfigDropFrames; if (controllerInit_) { - dropFrame = helper_->HideFramesModeSwitch(); + result->dropFrameCount_ = helper_->HideFramesModeSwitch(); mistrustCount_ = helper_->MistrustFramesModeSwitch(); } else { - dropFrame = helper_->HideFramesStartup(); + result->dropFrameCount_ = helper_->HideFramesStartup(); mistrustCount_ = helper_->MistrustFramesStartup(); } - result->data.push_back(dropFrame); - result->operation |= RPi::IPA_CONFIG_DROP_FRAMES; - /* These zero values mean not program anything (unless overwritten). */ struct AgcStatus agcStatus; agcStatus.shutter_time = 0.0; @@ -306,9 +306,9 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo, if (agcStatus.shutter_time != 0.0 && agcStatus.analogue_gain != 0.0) { ControlList ctrls(unicamCtrls_); applyAGC(&agcStatus, ctrls); - result->controls.push_back(ctrls); - result->operation |= RPi::IPA_CONFIG_SENSOR; + result->op_ |= ipa::rpi::ConfigSensor; + result->controls_ = std::move(ctrls); } lastMode_ = mode_; @@ -333,56 +333,38 @@ void IPARPi::unmapBuffers(const std::vector &ids) } } -void IPARPi::processEvent(const IPAOperationData &event) +void IPARPi::signalStatReady(const uint32_t bufferId) { - switch (event.operation) { - case RPi::IPA_EVENT_SIGNAL_STAT_READY: { - unsigned int bufferId = event.data[0]; - - if (++checkCount_ != frameCount_) /* assert here? */ - LOG(IPARPI, Error) << "WARNING: Prepare/Process mismatch!!!"; - if (frameCount_ > mistrustCount_) - processStats(bufferId); - - reportMetadata(); - - IPAOperationData op; - op.operation = RPi::IPA_ACTION_STATS_METADATA_COMPLETE; - op.data = { bufferId & RPi::BufferMask::ID }; - op.controls = { libcameraMetadata_ }; - queueFrameAction.emit(0, op); - break; - } + if (++checkCount_ != frameCount_) /* assert here? */ + LOG(IPARPI, Error) << "WARNING: Prepare/Process mismatch!!!"; + if (frameCount_ > mistrustCount_) + processStats(bufferId); - case RPi::IPA_EVENT_SIGNAL_ISP_PREPARE: { - unsigned int embeddedbufferId = event.data[0]; - unsigned int bayerbufferId = event.data[1]; + reportMetadata(); - /* - * At start-up, or after a mode-switch, we may want to - * avoid running the control algos for a few frames in case - * they are "unreliable". - */ - prepareISP(embeddedbufferId); - frameCount_++; - - /* Ready to push the input buffer into the ISP. */ - IPAOperationData op; - op.operation = RPi::IPA_ACTION_RUN_ISP; - op.data = { bayerbufferId & RPi::BufferMask::ID }; - queueFrameAction.emit(0, op); - break; - } + statsMetadataComplete.emit(bufferId & RPi::BufferMask::ID, libcameraMetadata_); +} - case RPi::IPA_EVENT_QUEUE_REQUEST: { - queueRequest(event.controls[0]); - break; - } +void IPARPi::signalQueueRequest(const ControlList &controls) +{ + queueRequest(controls); +} - default: - LOG(IPARPI, Error) << "Unknown event " << event.operation; - break; - } +void IPARPi::signalIspPrepare(const ipa::rpi::ISPConfig &data) +{ + unsigned int embeddedbufferId = data.embeddedbufferId_; + unsigned int bayerbufferId = data.bayerbufferId_; + + /* + * At start-up, or after a mode-switch, we may want to + * avoid running the control algos for a few frames in case + * they are "unreliable". + */ + prepareISP(embeddedbufferId); + frameCount_++; + + /* Ready to push the input buffer into the ISP. */ + runIsp.emit(bayerbufferId & RPi::BufferMask::ID); } void IPARPi::reportMetadata() @@ -693,10 +675,7 @@ void IPARPi::queueRequest(const ControlList &controls) void IPARPi::returnEmbeddedBuffer(unsigned int bufferId) { - IPAOperationData op; - op.operation = RPi::IPA_ACTION_EMBEDDED_COMPLETE; - op.data = { bufferId & RPi::BufferMask::ID }; - queueFrameAction.emit(0, op); + embeddedComplete.emit(bufferId & RPi::BufferMask::ID); } void IPARPi::prepareISP(unsigned int bufferId) @@ -757,12 +736,8 @@ void IPARPi::prepareISP(unsigned int bufferId) if (dpcStatus) applyDPC(dpcStatus, ctrls); - if (!ctrls.empty()) { - IPAOperationData op; - op.operation = RPi::IPA_ACTION_V4L2_SET_ISP; - op.controls.push_back(ctrls); - queueFrameAction.emit(0, op); - } + if (!ctrls.empty()) + setIsp.emit(ctrls); } } @@ -819,10 +794,7 @@ void IPARPi::processStats(unsigned int bufferId) ControlList ctrls(unicamCtrls_); applyAGC(&agcStatus, ctrls); - IPAOperationData op; - op.operation = RPi::IPA_ACTION_V4L2_SET_STAGGERED; - op.controls.push_back(ctrls); - queueFrameAction.emit(0, op); + setStaggered.emit(ctrls); } } @@ -1052,7 +1024,7 @@ void IPARPi::applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls) .grid_width = w, .grid_stride = w, .grid_height = h, - .dmabuf = lsTableHandle_.fd(), + .dmabuf = lsTableHandlePH_, .ref_transform = 0, .corner_sampled = 1, .gain_format = GAIN_FORMAT_U4P10 @@ -1129,9 +1101,9 @@ const struct IPAModuleInfo ipaModuleInfo = { "raspberrypi", }; -struct ipa_context *ipaCreate() +IPAInterface *ipaCreate() { - return new IPAInterfaceWrapper(std::make_unique()); + return new IPARPi(); } } /* extern "C" */ diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp index 6fcdf557..f11c4112 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -145,7 +147,11 @@ public: int loadIPA(); int configureIPA(const CameraConfiguration *config); - void queueFrameAction(unsigned int frame, const IPAOperationData &action); + void statsMetadataComplete(uint32_t bufferId, const ControlList &controls); + void runIsp(uint32_t bufferId); + void embeddedComplete(uint32_t bufferId); + void setIsp(const ControlList &controls); + void setStaggered(const ControlList &controls); /* bufferComplete signal handlers. */ void unicamBufferDequeue(FrameBuffer *buffer); @@ -157,6 +163,8 @@ public: void handleExternalBuffer(FrameBuffer *buffer, RPi::Stream *stream); void handleState(); + std::unique_ptr ipa_; + CameraSensor *sensor_; /* Array of Unicam and ISP device streams and associated buffers/streams. */ RPi::Device unicam_; @@ -453,6 +461,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() PipelineHandlerRPi::PipelineHandlerRPi(CameraManager *manager) : PipelineHandler(manager), unicam_(nullptr), isp_(nullptr) { + RPi::initializeRPiControls(); } CameraConfiguration *PipelineHandlerRPi::generateConfiguration(Camera *camera, @@ -941,7 +950,7 @@ bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) } /* Register the controls that the Raspberry Pi IPA can handle. */ - data->controlInfo_ = RPi::Controls; + data->controlInfo_ = RPi::controls; /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); @@ -1127,11 +1136,16 @@ void RPiCameraData::frameStarted(uint32_t sequence) int RPiCameraData::loadIPA() { - ipa_ = IPAManager::createIPA(pipe_, 1, 1); + ipa_ = IPAManager::createIPA(pipe_, 1, 1); + if (!ipa_) return -ENOENT; - ipa_->queueFrameAction.connect(this, &RPiCameraData::queueFrameAction); + ipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete); + ipa_->runIsp.connect(this, &RPiCameraData::runIsp); + ipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete); + ipa_->setIsp.connect(this, &RPiCameraData::setIsp); + ipa_->setStaggered.connect(this, &RPiCameraData::setStaggered); IPASettings settings{ .configurationFile = ipa_->configurationFile(sensor_->model() + ".json") @@ -1147,8 +1161,8 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) static_cast(config); std::map streamConfig; - std::map entityControls; - IPAOperationData ipaConfig = {}; + std::map entityControls; + ipa::rpi::ConfigInput ipaConfig; /* Get the device format to pass to the IPA. */ V4L2DeviceFormat sensorFormat; @@ -1168,7 +1182,7 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) entityControls.emplace(1, isp_[Isp::Input].dev()->controls()); /* Always send the user transform to the IPA. */ - ipaConfig.data = { static_cast(config->transform) }; + ipaConfig.transform_ = static_cast(config->transform); /* Allocate the lens shading table via dmaHeap and pass to the IPA. */ if (!lsTable_.isValid()) { @@ -1177,8 +1191,9 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) return -ENOMEM; /* Allow the IPA to mmap the LS table via the file descriptor. */ - ipaConfig.operation = RPi::IPA_CONFIG_LS_TABLE; - ipaConfig.data.push_back(static_cast(lsTable_.fd())); + ipaConfig.op_ |= ipa::rpi::ConfigLsTable; + ipaConfig.lsTableHandle_ = lsTable_; + ipaConfig.lsTableHandleStatic_ = lsTable_.fd(); } /* We store the CameraSensorInfo for digital zoom calculations. */ @@ -1189,34 +1204,35 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) } /* Ready the IPA - it must know about the sensor resolution. */ - IPAOperationData result; + ipa::rpi::ConfigOutput result; ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result); - unsigned int resultIdx = 0; - if (result.operation & RPi::IPA_CONFIG_STAGGERED_WRITE) { + if (result.op_ & ipa::rpi::ConfigStaggeredWrite) { /* * Setup our staggered control writer with the sensor default * gain and exposure delays. */ if (!staggeredCtrl_) { staggeredCtrl_.init(unicam_[Unicam::Image].dev(), - { { V4L2_CID_ANALOGUE_GAIN, result.data[resultIdx++] }, - { V4L2_CID_EXPOSURE, result.data[resultIdx++] } }); - sensorMetadata_ = result.data[resultIdx++]; + { { V4L2_CID_ANALOGUE_GAIN, + result.sensorConfig_.gainDelay_ }, + { V4L2_CID_EXPOSURE, + result.sensorConfig_.exposureDelay_ } }); + sensorMetadata_ = result.sensorConfig_.sensorMetadata_; } } - if (result.operation & RPi::IPA_CONFIG_SENSOR) { - const ControlList &ctrls = result.controls[0]; + if (result.op_ & ipa::rpi::ConfigSensor) { + const ControlList &ctrls = result.controls_; if (!staggeredCtrl_.set(ctrls)) LOG(RPI, Error) << "V4L2 staggered set failed"; } - if (result.operation & RPi::IPA_CONFIG_DROP_FRAMES) { + if (result.op_ & ipa::rpi::ConfigDropFrames) { /* Configure the number of dropped frames required on startup. */ - dropFrameCount_ = result.data[resultIdx++]; + dropFrameCount_ = result.dropFrameCount_; } /* @@ -1235,90 +1251,75 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config) return 0; } -void RPiCameraData::queueFrameAction([[maybe_unused]] unsigned int frame, - const IPAOperationData &action) +void RPiCameraData::statsMetadataComplete(uint32_t bufferId, const ControlList &controls) { - /* - * The following actions can be handled when the pipeline handler is in - * a stopped state. - */ - switch (action.operation) { - case RPi::IPA_ACTION_V4L2_SET_STAGGERED: { - const ControlList &controls = action.controls[0]; - if (!staggeredCtrl_.set(controls)) - LOG(RPI, Error) << "V4L2 staggered set failed"; - goto done; - } + if (state_ == State::Stopped) + handleState(); - case RPi::IPA_ACTION_V4L2_SET_ISP: { - ControlList controls = action.controls[0]; - isp_[Isp::Input].dev()->setControls(&controls); - goto done; - } - } + FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(bufferId); - if (state_ == State::Stopped) - goto done; + handleStreamBuffer(buffer, &isp_[Isp::Stats]); + + /* Fill the Request metadata buffer with what the IPA has provided */ + Request *request = requestQueue_.front(); + request->metadata() = std::move(controls); /* - * The following actions must not be handled when the pipeline handler - * is in a stopped state. + * Also update the ScalerCrop in the metadata with what we actually + * used. But we must first rescale that from ISP (camera mode) pixels + * back into sensor native pixels. + * + * Sending this information on every frame may be helpful. */ - switch (action.operation) { - case RPi::IPA_ACTION_STATS_METADATA_COMPLETE: { - unsigned int bufferId = action.data[0]; - FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(bufferId); + if (updateScalerCrop_) { + updateScalerCrop_ = false; + scalerCrop_ = ispCrop_.scaledBy(sensorInfo_.analogCrop.size(), + sensorInfo_.outputSize); + scalerCrop_.translateBy(sensorInfo_.analogCrop.topLeft()); + } + request->metadata().set(controls::ScalerCrop, scalerCrop_); - handleStreamBuffer(buffer, &isp_[Isp::Stats]); + state_ = State::IpaComplete; - /* Fill the Request metadata buffer with what the IPA has provided */ - Request *request = requestQueue_.front(); - request->metadata() = std::move(action.controls[0]); + handleState(); +} - /* - * Also update the ScalerCrop in the metadata with what we actually - * used. But we must first rescale that from ISP (camera mode) pixels - * back into sensor native pixels. - * - * Sending this information on every frame may be helpful. - */ - if (updateScalerCrop_) { - updateScalerCrop_ = false; - scalerCrop_ = ispCrop_.scaledBy(sensorInfo_.analogCrop.size(), - sensorInfo_.outputSize); - scalerCrop_.translateBy(sensorInfo_.analogCrop.topLeft()); - } - request->metadata().set(controls::ScalerCrop, scalerCrop_); +void RPiCameraData::runIsp(uint32_t bufferId) +{ + if (state_ == State::Stopped) + handleState(); - state_ = State::IpaComplete; - break; - } + FrameBuffer *buffer = unicam_[Unicam::Image].getBuffers().at(bufferId); - case RPi::IPA_ACTION_EMBEDDED_COMPLETE: { - unsigned int bufferId = action.data[0]; - FrameBuffer *buffer = unicam_[Unicam::Embedded].getBuffers().at(bufferId); - handleStreamBuffer(buffer, &unicam_[Unicam::Embedded]); - break; - } + LOG(RPI, Debug) << "Input re-queue to ISP, buffer id " << bufferId + << ", timestamp: " << buffer->metadata().timestamp; - case RPi::IPA_ACTION_RUN_ISP: { - unsigned int bufferId = action.data[0]; - FrameBuffer *buffer = unicam_[Unicam::Image].getBuffers().at(bufferId); + isp_[Isp::Input].queueBuffer(buffer); + ispOutputCount_ = 0; + handleState(); +} - LOG(RPI, Debug) << "Input re-queue to ISP, buffer id " << bufferId - << ", timestamp: " << buffer->metadata().timestamp; +void RPiCameraData::embeddedComplete(uint32_t bufferId) +{ + if (state_ == State::Stopped) + handleState(); - isp_[Isp::Input].queueBuffer(buffer); - ispOutputCount_ = 0; - break; - } + FrameBuffer *buffer = unicam_[Unicam::Embedded].getBuffers().at(bufferId); + handleStreamBuffer(buffer, &unicam_[Unicam::Embedded]); + handleState(); +} - default: - LOG(RPI, Error) << "Unknown action " << action.operation; - break; - } +void RPiCameraData::setIsp(const ControlList &controls) +{ + ControlList ctrls = controls; + isp_[Isp::Input].dev()->setControls(&ctrls); + handleState(); +} -done: +void RPiCameraData::setStaggered(const ControlList &controls) +{ + if (!staggeredCtrl_.set(controls)) + LOG(RPI, Error) << "V4L2 staggered set failed"; handleState(); } @@ -1420,10 +1421,7 @@ void RPiCameraData::ispOutputDequeue(FrameBuffer *buffer) * application until after the IPA signals so. */ if (stream == &isp_[Isp::Stats]) { - IPAOperationData op; - op.operation = RPi::IPA_EVENT_SIGNAL_STAT_READY; - op.data = { RPi::BufferMask::STATS | static_cast(index) }; - ipa_->processEvent(op); + ipa_->signalStatReady(RPi::BufferMask::STATS | static_cast(index)); } else { /* Any other ISP output can be handed back to the application now. */ handleStreamBuffer(buffer, stream); @@ -1595,7 +1593,6 @@ void RPiCameraData::checkRequestCompleted() void RPiCameraData::tryRunPipeline() { FrameBuffer *bayerBuffer, *embeddedBuffer; - IPAOperationData op; /* If any of our request or buffer queues are empty, we cannot proceed. */ if (state_ != State::Idle || requestQueue_.empty() || @@ -1642,9 +1639,7 @@ void RPiCameraData::tryRunPipeline() * queue the ISP output buffer listed in the request to start the HW * pipeline. */ - op.operation = RPi::IPA_EVENT_QUEUE_REQUEST; - op.controls = { request->controls() }; - ipa_->processEvent(op); + ipa_->signalQueueRequest(request->controls()); /* Set our state to say the pipeline is active. */ state_ = State::Busy; @@ -1656,10 +1651,10 @@ void RPiCameraData::tryRunPipeline() << " Bayer buffer id: " << bayerId << " Embedded buffer id: " << embeddedId; - op.operation = RPi::IPA_EVENT_SIGNAL_ISP_PREPARE; - op.data = { RPi::BufferMask::EMBEDDED_DATA | embeddedId, - RPi::BufferMask::BAYER_DATA | bayerId }; - ipa_->processEvent(op); + ipa::rpi::ISPConfig ispPrepare; + ispPrepare.embeddedbufferId_ = RPi::BufferMask::EMBEDDED_DATA | embeddedId; + ispPrepare.bayerbufferId_ = RPi::BufferMask::BAYER_DATA | bayerId; + ipa_->signalIspPrepare(ispPrepare); } bool RPiCameraData::findMatchingBuffers(FrameBuffer *&bayerBuffer, FrameBuffer *&embeddedBuffer) From patchwork Sat Dec 5 10:31:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10581 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 69461BDB20 for ; Sat, 5 Dec 2020 10:31:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3635B635FC; Sat, 5 Dec 2020 11:31:58 +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="EaEbxpS1"; 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 643C3635F6 for ; Sat, 5 Dec 2020 11:31:57 +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 63EF699A; Sat, 5 Dec 2020 11:31:55 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164316; bh=Jrq89+yD3C2+O1AFtHqooDn3L8tu2F9l/TFtKMZmkv8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EaEbxpS18oCx3EaJCk7lhZfpcSftQp0IWctIm1zZaen2J7UCi7qspeYm3aszyVrwz OhZxIUkCtgQdyaJIlrNyIY9yYAMPodSCU25ajBgYqUiX6LHtGPfHduM1ZznLzadnyd Tkck4k45FRPfRHwvGBFix+PqyQXDIfcwDm9DoS6M= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:01 +0900 Message-Id: <20201205103106.242080-19-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 18/23] libcamera: pipeline, ipa: vimc: Support the new 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 support to vimc pipeline handler and IPA for the new IPC mechanism. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- Changes in v5: - use the new generated header naming scheme - add dummy event, since event interfaces are now required to have at least one event Changes in v4: - rename Controls to controls - rename IPAVimcCallbackInterface to IPAVimcEventInterface - rename libcamera_generated_headers to libcamera_generated_ipa_headers in meson - use the new header name, vimc_ipa_interface.h Changes in v3: - change namespacing of base ControlInfoMap structure New in v2 --- include/libcamera/ipa/meson.build | 1 + include/libcamera/ipa/vimc.h | 8 ++++++++ include/libcamera/ipa/vimc.mojom | 13 +++++++++++++ src/ipa/vimc/meson.build | 2 +- src/ipa/vimc/vimc.cpp | 16 ++++------------ src/libcamera/pipeline/vimc/vimc.cpp | 8 +++++++- 6 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 include/libcamera/ipa/vimc.mojom diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index 5c838d5c..816cf4ab 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -26,6 +26,7 @@ ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module', ipa_mojom_files = [ 'raspberrypi.mojom', + 'vimc.mojom', ] ipa_mojoms = [] diff --git a/include/libcamera/ipa/vimc.h b/include/libcamera/ipa/vimc.h index 27a4a61d..2c5f3f8f 100644 --- a/include/libcamera/ipa/vimc.h +++ b/include/libcamera/ipa/vimc.h @@ -8,6 +8,8 @@ #ifndef __LIBCAMERA_IPA_VIMC_H__ #define __LIBCAMERA_IPA_VIMC_H__ +#include + #ifndef __DOXYGEN__ namespace libcamera { @@ -21,6 +23,12 @@ enum IPAOperationCode { IPAOperationStop, }; +namespace Vimc { + +static ControlInfoMap controls; + +} + } /* namespace libcamera */ #endif /* __DOXYGEN__ */ diff --git a/include/libcamera/ipa/vimc.mojom b/include/libcamera/ipa/vimc.mojom new file mode 100644 index 00000000..229659e7 --- /dev/null +++ b/include/libcamera/ipa/vimc.mojom @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +import "include/libcamera/ipa/core.mojom"; + +interface IPAVimcInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); +}; + +interface IPAVimcEventInterface { + dummyEvent(uint32 val); +}; diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build index 8c9df854..e982ebba 100644 --- a/src/ipa/vimc/meson.build +++ b/src/ipa/vimc/meson.build @@ -3,7 +3,7 @@ ipa_name = 'ipa_vimc' mod = shared_module(ipa_name, - 'vimc.cpp', + ['vimc.cpp', libcamera_generated_ipa_headers], name_prefix : '', include_directories : [ipa_includes, libipa_includes], dependencies : libcamera_dep, diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp index 483aeadd..6b088466 100644 --- a/src/ipa/vimc/vimc.cpp +++ b/src/ipa/vimc/vimc.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -24,7 +25,7 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPAVimc) -class IPAVimc : public IPAInterface +class IPAVimc : public IPAVimcInterface { public: IPAVimc(); @@ -35,15 +36,6 @@ public: int start() override; void stop() override; - void configure([[maybe_unused]] const CameraSensorInfo &sensorInfo, - [[maybe_unused]] const std::map &streamConfig, - [[maybe_unused]] const std::map &entityControls, - [[maybe_unused]] const IPAOperationData &ipaConfig, - [[maybe_unused]] IPAOperationData *result) override {} - void mapBuffers([[maybe_unused]] const std::vector &buffers) override {} - void unmapBuffers([[maybe_unused]] const std::vector &ids) override {} - void processEvent([[maybe_unused]] const IPAOperationData &event) override {} - private: void initTrace(); void trace(enum IPAOperationCode operation); @@ -139,9 +131,9 @@ const struct IPAModuleInfo ipaModuleInfo = { "vimc", }; -struct ipa_context *ipaCreate() +IPAInterface *ipaCreate() { - return new IPAInterfaceWrapper(std::make_unique()); + return new IPAVimc(); } } diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index 914b6b54..55df7192 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -34,6 +34,10 @@ #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" +#include +#include +#include + namespace libcamera { LOG_DEFINE_CATEGORY(VIMC) @@ -67,6 +71,8 @@ public: V4L2VideoDevice *video_; V4L2VideoDevice *raw_; Stream stream_; + + std::unique_ptr ipa_; }; class VimcCameraConfiguration : public CameraConfiguration @@ -428,7 +434,7 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator) std::unique_ptr data = std::make_unique(this, media); - data->ipa_ = IPAManager::createIPA(this, 0, 0); + data->ipa_ = IPAManager::createIPA(this, 0, 0); if (data->ipa_ != nullptr) { std::string conf = data->ipa_->configurationFile("vimc.conf"); data->ipa_->init(IPASettings{ conf }); From patchwork Sat Dec 5 10:31:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10582 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 C124EBDB20 for ; Sat, 5 Dec 2020 10:32:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8D7F2635F7; Sat, 5 Dec 2020 11:32: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="t4BvuejZ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A612635F4 for ; Sat, 5 Dec 2020 11:31: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 C3DA82A4; Sat, 5 Dec 2020 11:31:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164318; bh=FCh5WyC671ziL+Ng4eCTqejexqv8wG9GgPUICNjA/So=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t4BvuejZdS6n69JeKbGJRdODpjSu2SCH/T47c0nqr617U5IFLksqorBd8Ljf3dCFL fsXEJsXrBEtnB6TLvEr/pLR6fmCXAvravx5mhPfSD8UwjEGB02s9RYKfd6Hxorj1+k SzTuEtf/yGb+D78cpb2jNLrz1wTU5XzfYY3pfUPk= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:02 +0900 Message-Id: <20201205103106.242080-20-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 19/23] libcamera: pipeline, ipa: rkisp1: Support the new 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 support to the rkisp1 pipeline handler and IPA for the new IPC mechanism. Signed-off-by: Paul Elder --- Changes in v5: - import the generated header with the new file name Changes in v4: - rename Controls to controls - rename IPARkISP1CallbackInterface to IPARkISP1EventInterface - rename libcamera_generated_headers to libcamera_generated_ipa_headers in meson - use the new header name, rkisp1_ipa_interface.h Changes in v3: - change namespacing of base ControlInfoMap structure New in v2 --- include/libcamera/ipa/meson.build | 1 + include/libcamera/ipa/rkisp1.h | 18 ++++---- include/libcamera/ipa/rkisp1.mojom | 42 +++++++++++++++++++ src/ipa/rkisp1/meson.build | 2 +- src/ipa/rkisp1/rkisp1.cpp | 53 +++++++++++------------- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 44 +++++++++++--------- 6 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 include/libcamera/ipa/rkisp1.mojom diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index 816cf4ab..b5b8b0f2 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -26,6 +26,7 @@ ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module', ipa_mojom_files = [ 'raspberrypi.mojom', + 'rkisp1.mojom', 'vimc.mojom', ] diff --git a/include/libcamera/ipa/rkisp1.h b/include/libcamera/ipa/rkisp1.h index bb824f29..35f97ee4 100644 --- a/include/libcamera/ipa/rkisp1.h +++ b/include/libcamera/ipa/rkisp1.h @@ -7,15 +7,19 @@ #ifndef __LIBCAMERA_IPA_INTERFACE_RKISP1_H__ #define __LIBCAMERA_IPA_INTERFACE_RKISP1_H__ +#include + #ifndef __DOXYGEN__ -enum RkISP1Operations { - RKISP1_IPA_ACTION_V4L2_SET = 1, - RKISP1_IPA_ACTION_PARAM_FILLED = 2, - RKISP1_IPA_ACTION_METADATA = 3, - RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER = 4, - RKISP1_IPA_EVENT_QUEUE_REQUEST = 5, -}; +namespace libcamera { + +namespace RkISP1 { + +static ControlInfoMap controls; + +} /* namespace RkISP1 */ + +} /* namespace libcamera */ #endif /* __DOXYGEN__ */ diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom new file mode 100644 index 00000000..7f1a4ddd --- /dev/null +++ b/include/libcamera/ipa/rkisp1.mojom @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +import "include/libcamera/ipa/core.mojom"; + +interface IPARkISP1Interface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + configure(CameraSensorInfo sensorInfo, + map streamConfig, + map entityControls) => (); + + mapBuffers(array buffers); + unmapBuffers(array ids); + + [async] processEvent(RkISP1Event ev); +}; + +interface IPARkISP1EventInterface { + queueFrameAction(uint32 frame, RkISP1Action action); +}; + +enum RkISP1Operations { + RKISP1_IPA_ACTION_V4L2_SET = 1, + RKISP1_IPA_ACTION_PARAM_FILLED = 2, + RKISP1_IPA_ACTION_METADATA = 3, + RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER = 4, + RKISP1_IPA_EVENT_QUEUE_REQUEST = 5, +}; + +struct RkISP1Event { + RkISP1Operations op; + uint32 frame; + uint32 bufferId; + ControlList controls; +}; + +struct RkISP1Action { + RkISP1Operations op; + ControlList controls; +}; diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build index ed9a6b6b..3061d53c 100644 --- a/src/ipa/rkisp1/meson.build +++ b/src/ipa/rkisp1/meson.build @@ -3,7 +3,7 @@ ipa_name = 'ipa_rkisp1' mod = shared_module(ipa_name, - 'rkisp1.cpp', + ['rkisp1.cpp', libcamera_generated_ipa_headers], name_prefix : '', include_directories : [ipa_includes, libipa_includes], dependencies : libcamera_dep, diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 10301a19..c88f1271 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "libcamera/internal/log.h" @@ -28,7 +29,7 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPARkISP1) -class IPARkISP1 : public IPAInterface +class IPARkISP1 : public IPARkISP1Interface { public: int init([[maybe_unused]] const IPASettings &settings) override @@ -39,13 +40,11 @@ public: void stop() override {} void configure(const CameraSensorInfo &info, - const std::map &streamConfig, - const std::map &entityControls, - const IPAOperationData &ipaConfig, - IPAOperationData *response) override; + const std::map &streamConfig, + const std::map &entityControls) override; void mapBuffers(const std::vector &buffers) override; void unmapBuffers(const std::vector &ids) override; - void processEvent(const IPAOperationData &event) override; + void processEvent(const RkISP1Event &event) override; private: void queueRequest(unsigned int frame, rkisp1_params_cfg *params, @@ -78,10 +77,8 @@ private: * before accessing them. */ void IPARkISP1::configure([[maybe_unused]] const CameraSensorInfo &info, - [[maybe_unused]] const std::map &streamConfig, - const std::map &entityControls, - [[maybe_unused]] const IPAOperationData &ipaConfig, - [[maybe_unused]] IPAOperationData *result) + [[maybe_unused]] const std::map &streamConfig, + const std::map &entityControls) { if (entityControls.empty()) return; @@ -157,12 +154,12 @@ void IPARkISP1::unmapBuffers(const std::vector &ids) } } -void IPARkISP1::processEvent(const IPAOperationData &event) +void IPARkISP1::processEvent(const RkISP1Event &event) { - switch (event.operation) { + switch (event.op_) { case RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER: { - unsigned int frame = event.data[0]; - unsigned int bufferId = event.data[1]; + unsigned int frame = event.frame_; + unsigned int bufferId = event.bufferId_; const rkisp1_stat_buffer *stats = static_cast(buffersMemory_[bufferId]); @@ -171,17 +168,17 @@ void IPARkISP1::processEvent(const IPAOperationData &event) break; } case RKISP1_IPA_EVENT_QUEUE_REQUEST: { - unsigned int frame = event.data[0]; - unsigned int bufferId = event.data[1]; + unsigned int frame = event.frame_; + unsigned int bufferId = event.bufferId_; rkisp1_params_cfg *params = static_cast(buffersMemory_[bufferId]); - queueRequest(frame, params, event.controls[0]); + queueRequest(frame, params, event.controls_); break; } default: - LOG(IPARkISP1, Error) << "Unknown event " << event.operation; + LOG(IPARkISP1, Error) << "Unknown event " << event.op_; break; } } @@ -201,8 +198,8 @@ void IPARkISP1::queueRequest(unsigned int frame, rkisp1_params_cfg *params, params->module_en_update = RKISP1_CIF_ISP_MODULE_AEC; } - IPAOperationData op; - op.operation = RKISP1_IPA_ACTION_PARAM_FILLED; + RkISP1Action op; + op.op_ = RKISP1_IPA_ACTION_PARAM_FILLED; queueFrameAction.emit(frame, op); } @@ -254,13 +251,13 @@ void IPARkISP1::updateStatistics(unsigned int frame, void IPARkISP1::setControls(unsigned int frame) { - IPAOperationData op; - op.operation = RKISP1_IPA_ACTION_V4L2_SET; + RkISP1Action op; + op.op_ = RKISP1_IPA_ACTION_V4L2_SET; ControlList ctrls(ctrls_); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure_)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain_)); - op.controls.push_back(ctrls); + op.controls_ = ctrls; queueFrameAction.emit(frame, op); } @@ -272,9 +269,9 @@ void IPARkISP1::metadataReady(unsigned int frame, unsigned int aeState) if (aeState) ctrls.set(controls::AeLocked, aeState == 2); - IPAOperationData op; - op.operation = RKISP1_IPA_ACTION_METADATA; - op.controls.push_back(ctrls); + RkISP1Action op; + op.op_ = RKISP1_IPA_ACTION_METADATA; + op.controls_ = ctrls; queueFrameAction.emit(frame, op); } @@ -291,9 +288,9 @@ const struct IPAModuleInfo ipaModuleInfo = { "rkisp1", }; -struct ipa_context *ipaCreate() +IPAInterface *ipaCreate() { - return new IPAInterfaceWrapper(std::make_unique()); + return new IPARkISP1(); } } diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 6e74a49a..328161b2 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -144,9 +146,11 @@ public: RkISP1MainPath *mainPath_; RkISP1SelfPath *selfPath_; + std::unique_ptr ipa_; + private: void queueFrameAction(unsigned int frame, - const IPAOperationData &action); + const RkISP1Action &action); void metadataReady(unsigned int frame, const ControlList &metadata); }; @@ -412,7 +416,7 @@ private: int RkISP1CameraData::loadIPA() { - ipa_ = IPAManager::createIPA(pipe_, 1, 1); + ipa_ = IPAManager::createIPA(pipe_, 1, 1); if (!ipa_) return -ENOENT; @@ -425,11 +429,11 @@ int RkISP1CameraData::loadIPA() } void RkISP1CameraData::queueFrameAction(unsigned int frame, - const IPAOperationData &action) + const RkISP1Action &action) { - switch (action.operation) { + switch (action.op_) { case RKISP1_IPA_ACTION_V4L2_SET: { - const ControlList &controls = action.controls[0]; + const ControlList &controls = action.controls_; timeline_.scheduleAction(std::make_unique(frame, sensor_, controls)); @@ -442,10 +446,10 @@ void RkISP1CameraData::queueFrameAction(unsigned int frame, break; } case RKISP1_IPA_ACTION_METADATA: - metadataReady(frame, action.controls[0]); + metadataReady(frame, action.controls_); break; default: - LOG(RkISP1, Error) << "Unknown action " << action.operation; + LOG(RkISP1, Error) << "Unknown action " << action.op_; break; } } @@ -918,12 +922,10 @@ int PipelineHandlerRkISP1::start(Camera *camera) ret = 0; } - std::map entityControls; + std::map entityControls; entityControls.emplace(0, data->sensor_->controls()); - IPAOperationData ipaConfig; - data->ipa_->configure(sensorInfo, streamConfig, entityControls, - ipaConfig, nullptr); + data->ipa_->configure(sensorInfo, streamConfig, entityControls); return ret; } @@ -965,11 +967,12 @@ int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request) if (!info) return -ENOENT; - IPAOperationData op; - op.operation = RKISP1_IPA_EVENT_QUEUE_REQUEST; - op.data = { data->frame_, info->paramBuffer->cookie() }; - op.controls = { request->controls() }; - data->ipa_->processEvent(op); + RkISP1Event ev; + ev.op_ = RKISP1_IPA_EVENT_QUEUE_REQUEST; + ev.frame_ = data->frame_; + ev.bufferId_ = info->paramBuffer->cookie(); + ev.controls_ = request->controls(); + data->ipa_->processEvent(ev); data->timeline_.scheduleAction(std::make_unique(data->frame_, data, @@ -1191,10 +1194,11 @@ void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer) if (data->frame_ <= buffer->metadata().sequence) data->frame_ = buffer->metadata().sequence + 1; - IPAOperationData op; - op.operation = RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER; - op.data = { info->frame, info->statBuffer->cookie() }; - data->ipa_->processEvent(op); + RkISP1Event ev; + ev.op_ = RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER; + ev.frame_ = info->frame; + ev.bufferId_ = info->statBuffer->cookie(); + data->ipa_->processEvent(ev); } REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1) From patchwork Sat Dec 5 10:31:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10583 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 311E4BDB20 for ; Sat, 5 Dec 2020 10:32:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E8AE6635F7; Sat, 5 Dec 2020 11:32: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="s9VU/sa5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DEBE9635F4 for ; Sat, 5 Dec 2020 11:32:00 +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 848E32A4; Sat, 5 Dec 2020 11:31:59 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164320; bh=cmYoakgRntrNNmWkGKLuzjuA9LcqXotbsfEH4gmAp0Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s9VU/sa5omZ9+2LFu9vTS+/8lZgnoL3bC0bHuoAmGKC/ALCuKKfwshYtYAm3qQDY2 pwzlr6bTlkQ7INVZnlvwjnaP3Kf5Asp50VHNZGIrbgg6C4erzOz/XQbJulbMSUjuch kK4nleANP1twxgBAbJdyU5U9dmRMG3U0vtkE/X6o= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:03 +0900 Message-Id: <20201205103106.242080-21-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 20/23] tests: Add IPADataSerializer test 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" Test the IPADataSerializer for controls, vectors, maps, and PODs of built-in types. Signed-off-by: Paul Elder --- Changes in v5: - use ControlInfoMap serializer instead of const ControlInfoMap serializer Changes in v4: - use RPi::controls instead RPi::Controls Changes in v3: - use re-namespaced RPi::Controls New in v2 --- .../ipa_data_serializer_test.cpp | 465 ++++++++++++++++++ test/serialization/meson.build | 3 +- 2 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 test/serialization/ipa_data_serializer_test.cpp diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp new file mode 100644 index 00000000..0cc1cb91 --- /dev/null +++ b/test/serialization/ipa_data_serializer_test.cpp @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/thread.h" +#include "libcamera/internal/timer.h" + +#include "serialization_test.h" +#include "test.h" + +using namespace std; +using namespace libcamera; + +template +bool isVectorEqual(vector &vecA, vector &vecB) +{ + if (vecA.size() != vecB.size()) + return false; + + size_t len = vecA.size(); + for (unsigned int i = 0; i < len; i++) + if (vecA[i] != vecB[i]) + goto nequal; + + return true; + +nequal: + cerr << "lhs: { "; + for (const auto &value : vecA) + cerr << value << ", "; + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : vecB) + cerr << value << ", "; + cerr << "}" << endl; + + return false; +} + +template<> +bool isVectorEqual(vector &vecA, + vector &vecB) +{ + if (vecA.size() != vecB.size()) + return false; + + size_t len = vecA.size(); + for (unsigned int i = 0; i < len; i++) + if (!SerializationTest::equals(vecA[i], vecB[i])) + return false; + + return true; +} + +template +bool isMapEqual(map &mapA, map &mapB) +{ + if (mapA == mapB) + return true; + + cerr << "lhs: { "; + for (const auto &value : mapA) + cerr << value.first << " : " << value.second << ", "; + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : mapB) + cerr << value.first << " : " << value.second << ", "; + cerr << "}" << endl; + + return false; +} + +template +bool isMapToCimEqual(map &mapA, map &mapB) +{ + bool isEqual = true; + + auto itA = mapA.begin(); + auto itB = mapB.begin(); + while (true) { + bool endA = (itA == mapA.end()); + bool endB = (itB == mapB.end()); + + if (endA and endB) + break; + + if (!endA && (endB || itA->first < itB->first)) { + cerr << "key: " << itA->first << " not in mapB" << endl; + isEqual = false; + itA++; + continue; + } + + if (endA || itB->first < itA->first) { + cerr << "key: " << itB->first << " not in mapA" << endl; + isEqual = false; + itB++; + continue; + } + + if (!SerializationTest::equals(itA->second, itB->second)) { + cerr << "key " << itA->first + << " has different values" << endl; + isEqual = false; + } + + itA++; + itB++; + } + + return isEqual; +} + +template +bool isMapToVecEqual(map &mapA, map &mapB) +{ + if (mapA == mapB) + return true; + + cerr << "lhs: { "; + for (const auto &value : mapA) { + cerr << value.first << " : { "; + for (const auto &v : value.second) + cerr << v << ", "; + cerr << "}" << endl; + } + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : mapB) { + cerr << value.first << " : { "; + for (const auto &v : value.second) + cerr << v << ", "; + cerr << "}" << endl; + } + cerr << "}" << endl; + + return false; +} + +class IPADataSerializerTest : public CameraTest, public Test +{ +public: + IPADataSerializerTest() + : CameraTest("platform/vimc.0 Sensor B") + { + RPi::initializeRPiControls(); + } + +protected: + int init() override + { + return status_; + } + + int run() override + { + int finalRet = TestPass; + int ret; + + ret = testControls(); + if (ret != TestPass) + finalRet = ret; + + ret = testVector(); + if (ret != TestPass) + finalRet = ret; + + ret = testMap(); + if (ret != TestPass) + finalRet = ret; + + ret = testPod(); + if (ret != TestPass) + finalRet = ret; + + return finalRet; + } + +private: + ControlList generateControlListA() + { + /* Create a control list with three controls. */ + const ControlInfoMap &infoMap = camera_->controls(); + ControlList list(infoMap); + + list.set(controls::Brightness, 0.5f); + list.set(controls::Contrast, 1.2f); + list.set(controls::Saturation, 0.2f); + + return list; + } + + int testControls() + { + ControlSerializer cs; + + const ControlInfoMap &infoMap = camera_->controls(); + ControlList list = generateControlListA(); + + vector infoMapBuf; + tie(infoMapBuf, ignore) = + IPADataSerializer::serialize(infoMap, &cs); + + vector listBuf; + tie(listBuf, ignore) = + IPADataSerializer::serialize(list, *list.infoMap(), &cs); + + const ControlInfoMap infoMapOut = + IPADataSerializer::deserialize(infoMapBuf, &cs); + + ControlList listOut = IPADataSerializer::deserialize(listBuf, &cs); + + if (!SerializationTest::equals(infoMap, infoMapOut)) { + cerr << "Deserialized map doesn't match original" << endl; + return TestFail; + } + + if (!SerializationTest::equals(list, listOut)) { + cerr << "Deserialized list doesn't match original" << endl; + return TestFail; + } + + return TestPass; + } + + int testVector() + { + +#define TEST_VEC_SERDES(type, vec, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(vec, cs); \ +vector vec##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs); \ +ret = isVectorEqual(vec, vec##Out); \ +if (!ret) { \ + cerr << "Deserialized vector " << #vec << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + + ControlSerializer cs; + + /* + * We don't test FileDescriptor serdes because it dup()s, so we + * can't check for equality. + */ + vector vecUint8 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint16 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint32 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint64 = { 1, 2, 3, 4, 5, 6 }; + vector vecInt8 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt16 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt32 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt64 = { 1, 2, 3, -4, 5, -6 }; + vector vecFloat = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 }; + vector vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 }; + vector vecBool = { true, true, false, false, true, false }; + vector vecString = { "foo", "bar", "baz" }; + vector vecControlInfoMap = { + camera_->controls(), + RPi::controls, + }; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_VEC_SERDES(uint8_t, vecUint8, nullptr); + TEST_VEC_SERDES(uint16_t, vecUint16, nullptr); + TEST_VEC_SERDES(uint32_t, vecUint32, nullptr); + TEST_VEC_SERDES(uint64_t, vecUint64, nullptr); + TEST_VEC_SERDES(int8_t, vecInt8, nullptr); + TEST_VEC_SERDES(int16_t, vecInt16, nullptr); + TEST_VEC_SERDES(int32_t, vecInt32, nullptr); + TEST_VEC_SERDES(int64_t, vecInt64, nullptr); + TEST_VEC_SERDES(float, vecFloat, nullptr); + TEST_VEC_SERDES(double, vecDouble, nullptr); + TEST_VEC_SERDES(bool, vecBool, nullptr); + TEST_VEC_SERDES(string, vecString, nullptr); + TEST_VEC_SERDES(ControlInfoMap, vecControlInfoMap, &cs); + + return finalRet; + } + + int testMap() + { + +#define TEST_MAP_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + +#define TEST_MAP_CIM_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapToCimEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + +#define TEST_MAP_VEC_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapToVecEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + + ControlSerializer cs; + + /* + * Realistically, only string and integral keys. + * Test simple, complex, and nested compound value. + */ + map mapUintStr = + { {101, "foo"}, {102, "bar"}, {103, "baz"} }; + map mapIntStr = + { {101, "foo"}, {-102, "bar"}, {-103, "baz"} }; + map mapStrStr = + { {"a", "foo"}, {"b", "bar"}, {"c", "baz"} }; + map mapUintCIM = + { {201, camera_->controls()}, {202, RPi::controls} }; + map mapIntCIM = + { {201, camera_->controls()}, {-202, RPi::controls} }; + map mapStrCIM = + { {"a", camera_->controls()}, {"b", RPi::controls} }; + map> mapUintBVec = + { {301, { 1, 2, 3 }}, {302, {4, 5, 6}}, {303, {7, 8, 9}} }; + map> mapIntBVec = + { {301, { 1, 2, 3 }}, {-302, {4, 5, 6}}, {-303, {7, 8, 9}} }; + map> mapStrBVec = + { {"a", { 1, 2, 3 }}, {"b", {4, 5, 6}}, {"c", {7, 8, 9}} }; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_MAP_SERDES(uint64_t, string, mapUintStr, nullptr); + TEST_MAP_SERDES(int64_t, string, mapIntStr, nullptr); + TEST_MAP_SERDES(string, string, mapStrStr, nullptr); + TEST_MAP_CIM_SERDES(uint64_t, ControlInfoMap, mapUintCIM, &cs); + TEST_MAP_CIM_SERDES(int64_t, ControlInfoMap, mapIntCIM, &cs); + TEST_MAP_CIM_SERDES(string, ControlInfoMap, mapStrCIM, &cs); + TEST_MAP_VEC_SERDES(uint64_t, vector, mapUintBVec, nullptr); + TEST_MAP_VEC_SERDES(int64_t, vector, mapIntBVec, nullptr); + TEST_MAP_VEC_SERDES(string, vector, mapStrBVec, nullptr); + + return finalRet; + } + + int testPod() + { + +#define TEST_POD_SERDES(type, var) \ +tie(buf, fds) = IPADataSerializer::serialize(var); \ +type var##Out = \ + IPADataSerializer::deserialize(buf, fds); \ +ret = (var == var##Out); \ +if (!ret) { \ + cerr << "Deserialized " << #var << " as " << var##Out \ + << ", expected " << var << endl; \ + finalRet = TestFail; \ +} + + uint32_t u32min = numeric_limits::min(); + uint32_t u32max = numeric_limits::max(); + uint32_t u32one = 1; + int32_t i32min = numeric_limits::min(); + int32_t i32max = numeric_limits::max(); + int32_t i32one = 1; + + uint64_t u64min = numeric_limits::min(); + uint64_t u64max = numeric_limits::max(); + uint64_t u64one = 1; + int64_t i64min = numeric_limits::min(); + int64_t i64max = numeric_limits::max(); + int64_t i64one = 1; + + float flow = numeric_limits::lowest(); + float fmin = numeric_limits::min(); + float fmax = numeric_limits::max(); + float falmostOne = 1 + 1.0e-37; + double dlow = numeric_limits::lowest(); + double dmin = numeric_limits::min(); + double dmax = numeric_limits::max(); + double dalmostOne = 1 + 1.0e-307; + + bool t = true; + bool f = false; + + stringstream ss; + for (unsigned int i = 0; i < (1 << 21); i++) + ss << "0123456789"; + + string strLong = ss.str(); + string strEmpty = ""; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_POD_SERDES(uint32_t, u32min); + TEST_POD_SERDES(uint32_t, u32max); + TEST_POD_SERDES(uint32_t, u32one); + TEST_POD_SERDES(int32_t, i32min); + TEST_POD_SERDES(int32_t, i32max); + TEST_POD_SERDES(int32_t, i32one); + TEST_POD_SERDES(uint64_t, u64min); + TEST_POD_SERDES(uint64_t, u64max); + TEST_POD_SERDES(uint64_t, u64one); + TEST_POD_SERDES(int64_t, i64min); + TEST_POD_SERDES(int64_t, i64max); + TEST_POD_SERDES(int64_t, i64one); + TEST_POD_SERDES(float, flow); + TEST_POD_SERDES(float, fmin); + TEST_POD_SERDES(float, fmax); + TEST_POD_SERDES(float, falmostOne); + TEST_POD_SERDES(double, dlow); + TEST_POD_SERDES(double, dmin); + TEST_POD_SERDES(double, dmax); + TEST_POD_SERDES(double, dalmostOne); + TEST_POD_SERDES(bool, t); + TEST_POD_SERDES(bool, f); + TEST_POD_SERDES(string, strLong); + TEST_POD_SERDES(string, strEmpty); + + return finalRet; + } +}; + +TEST_REGISTER(IPADataSerializerTest) diff --git a/test/serialization/meson.build b/test/serialization/meson.build index a9d9cbcb..c140a31c 100644 --- a/test/serialization/meson.build +++ b/test/serialization/meson.build @@ -1,7 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 serialization_tests = [ - [ 'control_serialization', 'control_serialization.cpp' ], + [ 'control_serialization', 'control_serialization.cpp' ], + [ 'ipa_data_serializer_test', 'ipa_data_serializer_test.cpp' ], ] foreach t : serialization_tests From patchwork Sat Dec 5 10:31:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10584 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 AF902BDB20 for ; Sat, 5 Dec 2020 10:32:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7DCBC635FA; Sat, 5 Dec 2020 11:32:03 +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="ob9MiEqb"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9DD44635F7 for ; Sat, 5 Dec 2020 11:32:02 +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 4C9642A4; Sat, 5 Dec 2020 11:32:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164322; bh=X4krYc102aiM/pHeH3b5FqwvVyas206y5KBVn3DGO7Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ob9MiEqb/1/h7pHMu5eDclugJujNR2N2z+iTl7DktkzSsvhbjFp7/v4ZgmkiB74WB +AAnbdQuOS+RONtPCYDq0jbjevu8HfLq9uOoxhCFaXj6afCzU575RbZRy3kRvxpszZ l1QfODSV/P2mQssvYvUhrPY8Zu+jHiuOD1jZtrkE= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:04 +0900 Message-Id: <20201205103106.242080-22-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 21/23] tests: Add test for IPCPipeUnixSocket 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" Test the IPC functions of IPCPipeUnixSocket. Signed-off-by: Paul Elder --- Changes in v5: - rename IPAIPCUnixSocket to IPCPipeUnixSocket - use IPCMessage No change in v4 Changes in v3: - use readHeader, writeHeader, and eraseHeader as static class functions of IPAIPCUnixSocket New in v2 --- test/ipc/meson.build | 3 +- test/ipc/unixsocket_ipc.cpp | 237 ++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 test/ipc/unixsocket_ipc.cpp diff --git a/test/ipc/meson.build b/test/ipc/meson.build index 650df1d6..ecb6d022 100644 --- a/test/ipc/meson.build +++ b/test/ipc/meson.build @@ -1,7 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 ipc_tests = [ - [ 'unixsocket', 'unixsocket.cpp' ], + [ 'unixsocket_ipc', 'unixsocket_ipc.cpp' ], + [ 'unixsocket', 'unixsocket.cpp' ], ] foreach t : ipc_tests diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp new file mode 100644 index 00000000..827689f7 --- /dev/null +++ b/test/ipc/unixsocket_ipc.cpp @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * unixsocket_ipc.cpp - Unix socket IPC test + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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/process.h" +#include "libcamera/internal/thread.h" +#include "libcamera/internal/timer.h" +#include "libcamera/internal/utils.h" + +#include "test.h" + +#define CMD_EXIT 0 +#define CMD_GET_SYNC 1 +#define CMD_SET_ASYNC 2 + +using namespace std; +using namespace libcamera; + +class UnixSocketTestIPCSlave +{ +public: + UnixSocketTestIPCSlave() + : value_(1337), exitCode_(EXIT_FAILURE), exit_(false) + { + dispatcher_ = Thread::current()->eventDispatcher(); + ipc_.readyRead.connect(this, &UnixSocketTestIPCSlave::readyRead); + } + + int run(int fd) + { + if (ipc_.bind(fd)) { + cerr << "Failed to connect to IPC channel" << endl; + return EXIT_FAILURE; + } + + while (!exit_) + dispatcher_->processEvents(); + + ipc_.close(); + + return exitCode_; + } + +private: + void readyRead(IPCUnixSocket *ipc) + { + IPCUnixSocket::Payload message; + int ret; + + ret = ipc->receive(&message); + if (ret) { + cerr << "Receive message failed: " << ret << endl; + return; + } + + IPCMessage ipcMessage(message); + uint32_t cmd = ipcMessage.header().cmd; + + switch (cmd) { + case CMD_EXIT: { + exit_ = true; + break; + } + + case CMD_GET_SYNC: { + IPCMessage::Header header = { cmd, ipcMessage.header().cookie }; + IPCMessage response(header); + + vector buf; + tie(buf, ignore) = IPADataSerializer::serialize(value_); + response.data().insert(response.data().end(), buf.begin(), buf.end()); + + ret = ipc_.send(response.payload()); + if (ret < 0) { + cerr << "Reply failed" << endl; + stop(ret); + } + break; + } + + case CMD_SET_ASYNC: { + value_ = IPADataSerializer::deserialize(ipcMessage.data()); + break; + } + } + } + + void stop(int code) + { + exitCode_ = code; + exit_ = true; + } + + int32_t value_; + + IPCUnixSocket ipc_; + EventDispatcher *dispatcher_; + int exitCode_; + bool exit_; +}; + +class UnixSocketTestIPC : public Test +{ +protected: + int init() + { + return 0; + } + + int setVal(int32_t val) + { + IPCMessage buf; + tie(buf.data(), ignore) = IPADataSerializer::serialize(val); + + int ret = ipc_->sendAsync(CMD_SET_ASYNC, buf); + if (ret < 0) { + cerr << "Failed to call set value" << endl; + return ret; + } + + return 0; + } + + int getVal() + { + IPCMessage buf; + + int ret = ipc_->sendSync(CMD_GET_SYNC, {}, &buf); + if (ret < 0) { + cerr << "Failed to call get value" << endl; + return ret; + } + + return IPADataSerializer::deserialize(buf.data()); + } + + int exit() + { + int ret = ipc_->sendAsync(CMD_EXIT, {}); + if (ret < 0) { + cerr << "Failed to call exit" << endl; + return ret; + } + + return 0; + } + + int run() + { + char selfpath[100]; + memset(selfpath, 0, sizeof(selfpath)); + int ret = readlink("/proc/self/exe", selfpath, sizeof(selfpath)); + if (ret < 0) { + int err = errno; + cerr << "Failed to get path: " << strerror(err) << endl; + return TestFail; + } + + ipc_ = std::make_unique("", selfpath); + if (!ipc_->isConnected()) { + cerr << "Failed to create IPCPipe" << endl; + return TestFail; + } + + ret = getVal(); + if (ret < 0) { + cerr << "Failed to get initial value: " << strerror(-ret) << endl; + return TestFail; + } + if (ret != 1337) { + cerr << "Wrong inital value, expected 1337, got " << ret << endl; + return TestFail; + } + + ret = setVal(9001); + if (ret < 0) { + cerr << "Failed to set value: " << strerror(-ret) << endl; + return TestFail; + } + + ret = getVal(); + if (ret < 0) { + cerr << "Failed to get value: " << strerror(-ret) << endl; + return TestFail; + } + if (ret != 9001) { + cerr << "Wrong set value, expected 9001, got " << ret << endl; + return TestFail; + } + + ret = exit(); + if (ret < 0) { + cerr << "Failed to exit: " << strerror(-ret) << endl; + return TestFail; + } + + return TestPass; + } + +private: + ProcessManager processManager_; + + unique_ptr ipc_; +}; + +/* + * Can't use TEST_REGISTER() as single binary needs to act as both proxy + * master and slave. + */ +int main(int argc, char **argv) +{ + /* IPCPipeUnixSocket passes IPA module path in argv[1] */ + if (argc == 3) { + int ipcfd = std::stoi(argv[2]); + UnixSocketTestIPCSlave slave; + return slave.run(ipcfd); + } + + return UnixSocketTestIPC().execute(); +} From patchwork Sat Dec 5 10:31:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10585 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 15910BDB20 for ; Sat, 5 Dec 2020 10:32:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D6FD76361A; Sat, 5 Dec 2020 11:32: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="UeNkejsG"; 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 6AFB2635F4 for ; Sat, 5 Dec 2020 11:32: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 061E42A4; Sat, 5 Dec 2020 11:32:02 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164324; bh=Ght9x3qGM26PpQO5bFBauciDuYKi8XU2OBTc0Nqc+O0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UeNkejsGUebWbudyPCUePIOvRwcl5/FB3GCs7/vIBQKXCURWz9NAhTLlMQFc+pl+T 5QBBk7uM1tzP342BnQBkOMSydjN/9jK3djiSkN9ToTsW8taUB4CnwdsbHxa1aF9cm4 ST4vVIBVOV4kW7lZ7KHu94UPWpD3LEMUK7e3oZME= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:05 +0900 Message-Id: <20201205103106.242080-23-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 22/23] Documentation: Add IPA writers guide 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 guide about writing IPAs. Signed-off-by: Paul Elder --- Changes in v5: - fix doxygen compile errors - update example struct names from raspberry pi changes - add todo for restricting pre-start() to sync and post-start() to async Changes in v4.1: - Add section on namespacing, custom data structures, compiling, and usage of the data structures and interface - Add examples to the main ipa interface and event ipa interface sections New in v4 --- Documentation/guides/ipa.rst | 479 +++++++++++++++++++++++++++++++++++ Documentation/index.rst | 1 + Documentation/meson.build | 1 + 3 files changed, 481 insertions(+) create mode 100644 Documentation/guides/ipa.rst diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst new file mode 100644 index 00000000..e857e30b --- /dev/null +++ b/Documentation/guides/ipa.rst @@ -0,0 +1,479 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +IPA Writers Guide +================= + +IPA are Image Processing Algorithm modules. They provide functionality that +the pipeline handler can use for image processing. + +This guide so far only covers definition the IPA interface, and how to plumb +the connection between the pipeline handler and the IPA. + +The IPA interface and protocol +------------------------------ + +The IPA interface defines the interface between the pipeline handler and the +IPA. Specifically, it defines the functions that the IPA exposes that the +pipeline handler can call, and the Signals that the pipeline handler can +connect to to receive data from the IPA asyncrhonously. In addition, it +contains any custom data structures that the pipeline handler and IPA may +pass to each other. + +The IPA protocol refers to the agreement between the pipeline handler and the +IPA regarding the expected response(s) from the IPA for given calls to the IPA. +This protocol doesn't need to be declared anywhere in code, but it would be +useful to document it, as there may be many different IPAs for one pipeline +handler. + +The IPA interface must be defined in a mojom file. The interface includes: +- the functions that the pipeline handler can call from the IPA +- Signals in the pipeline handler that the IPA can emit +- any data structures that are to be passed between the pipeline handler and the IPA +All IPA of a given pipeline handler use the same IPA interface. The IPA +interface definition is thus likely to be written by the pipeline handler +author, based on how they imagine the pipeline handler will interact with +the IPA. + +The entire IPA interface, including the functions, Signals, and any custom +structs shall be defined in in a file named {pipeline_name}.mojom under +include/libcamera/ipa/ using the mojo interface definition language (IDL). This +will be covered in detail in the following sections. + +Namespacing +----------- + +It is recommended to use namespacing, to avoid potential collisions with +libcamera types. Use mojo's module directive for this. + +It must be the first meaningful line in the mojo data definition file, for +example (defining a raspberry pi IPA): + +.. code-block:: none + + module ipa.rpi; + +This will become the ipa::rpi namespace in C++ code. + +Data containers +--------------- + +Since the data passed between the pipeline handler and the IPA must support +serialization, any custom data containers must be defined with the mojo IDL. + +The following list of libcamera objects are supported in the interface +definition, and may be used as function parameter types or struct field types: + +- CameraSensorInfo +- ControlInfoMap +- ControlList +- FileDescriptor +- IPABuffer +- IPASettings +- IPAStream + +To use them, core.mojom must be included in the mojo data definition file: + +.. code-block:: none + + import "include/libcamera/ipa/core.mojom"; + +Other custom structs may be defined and used as well. There is no requirement +that they must be defined before usage. enums and structs are supported. + +The following is an example of a definition of an enum, for the purpose of +being used as flags: + +.. code-block:: none + + enum ConfigParameters { + ConfigLsTable = 0x01, + ConfigStaggeredWrite = 0x02, + ConfigSensor = 0x04, + ConfigDropFrames = 0x08, + }; + +The following is an example of a definition of a struct: + +.. code-block:: none + + struct ConfigInput { + uint32 op; + uint32 transform; + FileDescriptor lsTableHandle; + int32 lsTableHandleStatic = -1; + map streamConfig; + array buffers; + }; + +This example has some special things about it. First of all, it uses the +FileDescriptor data type. This type must be used to ensure that the file +descriptor that it contains is translated property across the IPC boundary +(when the IPA is in an isolated process). + +This does mean that if the file descriptor should be sent without being +translated (for example, for the IPA to tell the pipeline handler which +fd *that the pipeline handler holds* to act on), then it must be in a +regular int32 type. + +This example also illustrates that struct fields may have default values, as +is assigned to lsTableHandleStatic. This is the value that the field will +take when the struct is constructed with the default constructor. + +Arrays and maps are supported as well. They are translated to C++ vectors and +maps, respectively. The members of the arrays and maps are embedded, and cannot +be const. + +Note that nullable fields, static-length arrays, handles, and unions, which +are supported by mojo, are not supported by our code generator. + +TODO what about versioning, and numbered fields? + +The Main IPA interface +---------------------- + +The IPA interface is split in two parts, the Main IPA interface, which +describes the functions that the pipeline handler can call from the IPA, +and the Event IPA interface, which describes the Signals in the pipeline +handler that the IPA can emit. Both must be defined. This section focuses +on the Main IPA interface. + +The main interface must be named as IPA{pipeline_name}Interface. + +At minimum, the following three functions must be present (and implemented): +- init(IPASettings settings) => (int32 ret); +- start() => (int32 ret); +- stop(); + +All three of these functions are synchronous. + +TODO Restrict pre-start to synchronous, and post-start to asynchronous + +All input parameters will become const references, except for arithmetic types, +which will be passed by value. Output parameters will become pointers, unless +there is only one primitive output parameter, in which case it will become a +a regular return value. + +const is not allowed inside of arrays and maps. mojo arrays will become C++ +std::vector<>. + +By default, all methods defined in the main interface are synchronous. This +means that in the case of IPC (ie. isolated IPA), the function call will not +return until the return value or output parameters are ready. To specify an +asynchronous function, the [async] attribute can be used. Asynchronous +methods must not have any return value or output parameters, since in the +case of IPC the call needs to return immediately. + +It is also possible that the IPA will not be run in isolation. In this case, +the IPA thread will not exist until start() is called. This means that in the +case of no isolation, asynchronous calls cannot be made before start(). Since +the IPA interface must be the same regardless of isolation, the same +restriction applies to the case of isolation, and any function that will be +called before start() must be synchronous. + +In addition, any call made after start() and before stop() must be +asynchronous. The motivation for this is to avoid damaging real time +performance of the pipeline handler. If the pipeline handler wants some data +from the IPA, the IPA should return the data asynchronously via an event +(see "The Event IPA interface"). + +In addition the following must be defined in {pipeline_name}.h in the +libcamera namespace: + +.. code-block:: C++ + + static ControlInfoMap {pipeline_name}::controls; + +It may be empty. This will be the default ControlInfoMap used when +serializing and deserializing any ControlLists if it is not specified +in the ControlList. + +The following is an example of a main interface definition: + +.. code-block:: none + + interface IPARPiInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + configure(CameraSensorInfo sensorInfo, + map streamConfig, + map entityControls, + ConfigInput ipaConfig) + => (ConfigOutput results); + + mapBuffers(array buffers); + unmapBuffers(array ids); + + [async] signalStatReady(uint32 bufferId); + [async] signalQueueRequest(ControlList controls); + [async] signalIspPrepare(ISPConfig data); + }; + + +The first three functions are the required functions. Functions do not need to +have return values, like stop(), mapBuffers(), and unmapBuffers(). In the case +of asynchronous functions, as explained before, they *must not* have return +values. + +The Event IPA interface +----------------------- + +The event IPA interface describes the Signals in the pipeline handler that the +IPA can emit. It must be defined. If there are no event functions, then it may +be empty. These emissions are meant to notify the pipeline handler of some +event, such as reqeust data is ready, and *must not* be used to drive the +camera pipeline from the IPA. + +The event interface must be named as IPA{pipeline_name}EventInterface. + +Methods defined in the event interface are implictly asynchronous. +They thus cannot return any value. Specifying the [async] tag is not +necessary. + +Methods defined in the event interface will become Signals in the IPA +interface. The IPA can emit signals, while the pipeline handler can connect +slots to them. + +The following is an example of an event interface definition: + +.. code-block:: none + + interface IPARPiEventInterface { + statsMetadataComplete(uint32 bufferId, ControlList controls); + runIsp(uint32 bufferId); + embeddedComplete(uint32 bufferId); + setIsp(ControlList controls); + setStaggered(ControlList controls); + }; + +Compiling the IPA interface +--------------------------- + +After the IPA interface is defined in include/libcamera/ipa/{pipeline_name}.mojom, +and entry for it must be added in meson so that it can be compiled. The filename +must be added to the ipa_mojom_files object in include/libcamera/ipa/meson.build. + +For example, adding the raspberrypi.mojom file to meson: + +.. code-block:: none + + ipa_mojom_files = [ + 'raspberrypi.mojom', + ] + +This will cause the mojo data definition file to be compiled. Specifically, it +generated five files: +- a header describing the custom data structures, and the complete IPA interface +- a serializer implementing de/serialization for the custom data structures +- a proxy header describing a specialized IPA proxy +- a proxy source implementing the IPA proxy +- a proxy worker source implementing the other end of the IPA proxy + +The pipeline handler and the IPA only require the header and the proxy header. +The serializer is only used internally by the proxy. + +Using the custom data structures +-------------------------------- + +To use the custom data structures that are defined in the mojo data definition +file, the follow header must be included: + +.. code-block:: C++ + + #include + +The POD types of the structs simply become their C++ counterparts, eg. uint32 +in mojo will become uint32_t in C++. mojo map becomes C++ std::map, and mojo +array becomes C++ std::vector. All members of maps and vectors are embedded, +and are not pointers. The members cannot be const. + +All fields of structs are the name as specified in the data definition file, +with an underscore at the end. For example, the following struct as defined +in the mojo file: + +.. code-block:: none + + struct SensorConfig { + uint32 gainDelay = 1; + uint32 exposureDelay; + uint32 sensorMetadata; + }; + +Will become this in C++: + +.. code-block:: C++ + + struct SensorConfig { + uint32_t gainDelay_; + uint32_t exposureDelay_; + uint32_t sensorMetadata_; + }; + +The generated structs will also have two constructors, a constructor that +fills all fields with the default values, and a second constructor that takes +a value for every field. The default value constructor will fill in the fields +with the specified default value, if it exists. In the above example, `gainDelay_` +will be initialized to 1. If no default value is specified, then it will be +filled in as zero (or -1 for a FileDescriptor type). + +All fields and constructors/deconstructors in these generated structs are public. + +Using the IPA interface (pipeline handler) +------------------------------------------ + +The following headers are necessary to use an IPA in the pipeline handler +(with raspberrypi as an example): + +.. code-block:: C++ + + #include + #include + +The first header includes definitions of the custom data structures, and +the definition of the complete IPA interface (including both the Main and +the Event IPA interfaces). The name of the header file is comes from the name +of the mojom file, which in this case was raspberrypi.mojom. + +The second header inclues the definition of the specialized IPA proxy. It +exposes the complete IPA interface. We will see how to use it in this section. + +In the pipeline handler, we first need to construct a specialized IPA proxy. +From the point of view of the pipeline hander, this is the object that is the +IPA. + +To do so, we invoke the IPAManager: + +.. code-block:: C++ + + std::unique_ptr ipa_ = + IPAManager::createIPA(pipe_, 1, 1); + +The ipa::rpi namespace comes from the namespace that we defined in the mojo +data definition file, in the "Namespacing" section. The name of the proxy, +IPAProxyRPi, comes from the name given to the main IPA interface, +IPARPiInterface, in the "The Main IPA interface" section. + +The return value of IPAManager::createIPA shall be error-checked, to confirm +that the returned pointer is not a nullptr. + +After this, before initializing the IPA, slots should be connected to all of +the IPA's Signals, as defined in the Event IPA interface: + +.. code-block:: C++ + + ipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete); + ipa_->runIsp.connect(this, &RPiCameraData::runIsp); + ipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete); + ipa_->setIsp.connect(this, &RPiCameraData::setIsp); + ipa_->setStaggered.connect(this, &RPiCameraData::setStaggered); + +The slot functions have a function signature based on the function definition +in the Event IPA interface. All plain old data (POD) types are as-is (with +their C++ versions, eg. uint32 -> uint32_t), and all structs are const references. + +For example, for the following entry in the Event IPA interface: + +.. code-block:: none + + statsMetadataComplete(uint32 bufferId, ControlList controls); + +A function with the following function signature shall be connected to the +signal: + +.. code-block:: C++ + + statsMetadataComplete(uint32_t bufferId, const ControlList &controls); + +After connecting the slots to the signals, the IPA should be initialized +(fill in settings accordingly): + +.. code-block:: C++ + + IPASettings settings{}; + ipa_->init(settings); + +At this point, any IPA functions that were defined in the Main IPA interface +can be called as if they were regular member functions, for example: + +.. code-block:: C++ + + ipa_->start(); + ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result); + ipa_->signalStatReady(RPi::BufferMask::STATS | static_cast(index)); + +Remember that any functions designated as asynchronous *must not* be called +before start(). + +TODO anything special about start() and stop() ? + +Using the IPA interface (IPA) +----------------------------- + +The following header is necessary to implement an IPA (with raspberrypi as +an example): + +.. code-block:: C++ + + #include + +This header includes definitions of the custom data structures, and +the definition of the complete IPA interface (including both the Main and +the Event IPA interfaces). The name of the header file is comes from the name +of the mojom file, which in this case was raspberrypi.mojom. + +The IPA must implement the IPA interface class that is defined in the header. +In the case of our example, that is ipa::rpi::IPARPiInterface. The ipa::rpi +namespace comes from the namespace that we defined in the mojo data definition +file, in the "Namespacing" section. The name of the interface is the same as +the name given to the Main IPA interface. + +The function signature rules are the same as for the slots in the pipeline +handler side; PODs are passed by value, and structs are passed by const +reference. For the Main IPA interface, output values are also allowed (only +for synchronous calls), so there may be output parameters as well. If the +output parameter is a single POD it will be returned by value, otherwise +(multiple PODs or struct(s)) it will be returned by output parameter pointers. + +For example, for the following function specification in the Main IPA interface +definition: + +.. code-block:: none + + configure(CameraSensorInfo sensorInfo, + uint32 exampleNumber, + map streamConfig, + map entityControls, + ConfigInput ipaConfig) + => (ConfigOutput results); + +We will need to implement a function with the following function signature: + +.. code-block:: C++ + + void configure(const CameraSensorInfo &sensorInfo, + uint32_t exampleNumber, + const std::map &streamConfig, + const std::map &entityControls, + const ipa::rpi::ConfigInput &data, + ipa::rpi::ConfigOutput *response); + +The return value is void, because the output parameter is not a single POD. +Instead, it becomes an output parameter pointer. The non-POD input parameters +become const references, and the POD input parameter is passed by value. + +At any time (though usually only in response to an IPA call), the IPA may send +data to the pipeline handler by emitting signals. These signals are defined +in the C++ IPA interface class (which is in the generated and included header). + +For example, for the following function defined in the Event IPA interface: + +.. code-block:: none + + statsMetadataComplete(uint32 bufferId, ControlList controls); + +We can emit a signal like so: + +.. code-block:: C++ + + statsMetadataComplete.emit(bufferId & RPi::BufferMask::ID, libcameraMetadata_); diff --git a/Documentation/index.rst b/Documentation/index.rst index ff697d4f..8bc8922e 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -16,4 +16,5 @@ Developer Guide Application Writer's Guide Pipeline Handler Writer's Guide + IPA Writer's guide Tracing guide diff --git a/Documentation/meson.build b/Documentation/meson.build index 26a12fcd..74381f32 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -54,6 +54,7 @@ if sphinx.found() 'docs.rst', 'index.rst', 'guides/introduction.rst', + 'guides/ipa.rst', 'guides/application-developer.rst', 'guides/pipeline-handler.rst', 'guides/tracing.rst', From patchwork Sat Dec 5 10:31:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10586 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 9A7CABDB20 for ; Sat, 5 Dec 2020 10:32:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 623EC6360B; Sat, 5 Dec 2020 11:32: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="gVqKM5oL"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2D189635FE for ; Sat, 5 Dec 2020 11:32:06 +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 D41CC2A4; Sat, 5 Dec 2020 11:32:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1607164325; bh=w4O8NFX8tlOHCCs9jFzGKMAlEDd+urJLOME4ogM1QJ8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gVqKM5oLt+FE7jZ596sJG9aMzEjexDm+9UQDoXtNqPcPmyxV0Czxy5ZKZbvNI3JY5 IRN/fyO4O410viQDi2KvgB7qNviBDRBUbLcOHZOnwbUmoR2JfWsoADBMNsw/EvTD4C B4qrYKwExU0anbbhE22kP32aLtgE/6E3FUPoJWbk= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Sat, 5 Dec 2020 19:31:06 +0900 Message-Id: <20201205103106.242080-24-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201205103106.242080-1-paul.elder@ideasonboard.com> References: <20201205103106.242080-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v5 23/23] tests: Test IPA serializer generation 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 test to confirm that serializer and header generation works properly for mojom definition files, and that the serializer works properly. Signed-off-by: Paul Elder --- Changes in v5: - add dummy event to event interface New in v4 --- .../generated_serializer_test.cpp | 128 ++++++++++++++++++ .../generated_serializer/meson.build | 49 +++++++ .../generated_serializer/vimc.mojom | 24 ++++ test/serialization/meson.build | 2 + 4 files changed, 203 insertions(+) create mode 100644 test/serialization/generated_serializer/generated_serializer_test.cpp create mode 100644 test/serialization/generated_serializer/meson.build create mode 100644 test/serialization/generated_serializer/vimc.mojom diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp new file mode 100644 index 00000000..57c61b02 --- /dev/null +++ b/test/serialization/generated_serializer/generated_serializer_test.cpp @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * generated_serializer_test.cpp - Test generated serializer + */ + +#include +#include +#include + +#include "test.h" + +#include "vimc_test_ipa_interface.h" +#include "vimc_test_ipa_serializer.h" + +using namespace std; +using namespace libcamera; + +class IPAGeneratedSerializerTest : public Test +{ +protected: + int init() override + { + return TestPass; + } + + int run() override + { + +#define TEST_FIELD_EQUALITY(struct1, struct2, field) \ +if (struct1.field != struct2.field) { \ + cerr << #field << " field incorrect: expected \"" \ + << t.field << "\", got \"" << u.field << "\"" << endl;\ + return TestFail; \ +} + + TestStruct t, u; + + t.m_ = { + { "a", "z" }, + { "b", "z" }, + { "c", "z" }, + { "d", "z" }, + { "e", "z" }, + }; + + t.a_ = { "a", "b", "c", "d", "e" }; + + t.s1_ = "hello world"; + t.s2_ = "goodbye"; + t.s3_ = "lorem ipsum"; + t.i_ = 58527; + + vector serialized; + + tie(serialized, ignore) = + IPADataSerializer::serialize(t); + + u = IPADataSerializer::deserialize(serialized); + + if (!equals(t.m_, u.m_)) + return TestFail; + + if (!equals(t.a_, u.a_)) + return TestFail; + + TEST_FIELD_EQUALITY(t, u, s1_); + TEST_FIELD_EQUALITY(t, u, s2_); + TEST_FIELD_EQUALITY(t, u, s3_); + TEST_FIELD_EQUALITY(t, u, i_); + + return TestPass; + } + +private: + bool equals(const map &lhs, const map &rhs) + { + bool eq = lhs.size() == rhs.size() && + equal(lhs.begin(), lhs.end(), rhs.begin(), + [](auto &a, auto &b) { return a.first == b.first && + a.second == b.second; }); + + if (eq) + return true; + + cerr << "lhs:" << endl; + for (const auto &pair : lhs) + cerr << "- " << pair.first << ": " + << pair.second << endl; + + cerr << "rhs:" << endl; + for (const auto &pair : rhs) + cerr << "- " << pair.first << ": " + << pair.second << endl; + + return false; + } + + bool equals(const vector &lhs, const vector &rhs) + { + bool eq = lhs.size() == rhs.size(); + + if (!eq) { + cerr << "sizes not equal" << endl; + return false; + } + + for (unsigned int i = 0; i < lhs.size(); i++) + if (lhs[i] != rhs[i]) + eq = false; + + if (eq) + return true; + + cerr << "lhs:" << endl; + for (const auto &str : lhs) + cerr << "- " << str << endl; + + cerr << "rhs:" << endl; + for (const auto &str : rhs) + cerr << "- " << str << endl; + + return false; + } +}; + +TEST_REGISTER(IPAGeneratedSerializerTest) diff --git a/test/serialization/generated_serializer/meson.build b/test/serialization/generated_serializer/meson.build new file mode 100644 index 00000000..68bcf23d --- /dev/null +++ b/test/serialization/generated_serializer/meson.build @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: CC0-1.0 + +# vimc.mojom-module +mojom = custom_target('vimc_test_mojom_module', + input : 'vimc.mojom', + output : 'vimc.mojom-module', + command : [ + mojom_parser, + '--output-root', meson.build_root(), + '--input-root', meson.source_root(), + '--mojoms', '@INPUT@' + ]) + +# vimc_test_ipa_interface.h +header = custom_target('vimc_test_ipa_interface_h', + input : mojom, + output : 'vimc_test_ipa_interface.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_header', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + +# vimc_test_ipa_serializer.h +serializer = custom_target('vimc_test_ipa_serializer_h', + input : mojom, + output : 'vimc_test_ipa_serializer.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_serializer', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + +exe = executable('generated_serializer_test', + ['generated_serializer_test.cpp', header, serializer], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + +test('generated_serializer_test', exe, + suite : 'generated_serializer', is_parallel : false) diff --git a/test/serialization/generated_serializer/vimc.mojom b/test/serialization/generated_serializer/vimc.mojom new file mode 100644 index 00000000..c6f29d61 --- /dev/null +++ b/test/serialization/generated_serializer/vimc.mojom @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +struct IPASettings {}; + +struct TestStruct { + map m; + array a; + string s1; + string s2; + int32 i; + string s3; +}; + +interface IPAVimcInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + test(TestStruct s); +}; + +interface IPAVimcEventInterface { + dummyEvent(uint32 val); +}; diff --git a/test/serialization/meson.build b/test/serialization/meson.build index c140a31c..57726b12 100644 --- a/test/serialization/meson.build +++ b/test/serialization/meson.build @@ -1,5 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 +subdir('generated_serializer') + serialization_tests = [ [ 'control_serialization', 'control_serialization.cpp' ], [ 'ipa_data_serializer_test', 'ipa_data_serializer_test.cpp' ],