[libcamera-devel,v7,02/10] utils: ipc: add templates for code generation for IPC mechanism
diff mbox series

Message ID 20210211071805.34963-3-paul.elder@ideasonboard.com
State Changes Requested
Headers show
Series
  • IPA isolation: Part 1: Core components
Related show

Commit Message

Paul Elder Feb. 11, 2021, 7:17 a.m. UTC
Add templates to mojo to generate code for the IPC mechanism. These
templates generate:
- module header
- module serializer
- IPA proxy cpp, header, and worker

Given an input data definition mojom file for a pipeline.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Acked-by: Jacopo Mondi <jacopo@jmondi.org>
Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

---
Changes in v7:
- cosmetic changes, add a few todos
- use the new sendSync/sendAsync IPCPipe API
  - save the sequence number in the proxy
  - construct the header before calling sendSync/sendAsync (from the
    proxy)
- replace genHeader and genSerdes with skipHeader and skipSerdes in
  core.mojom

Changes in v6:
- add templates for core_ipa_interface.h and core_ipa_serializer.h
  - for libcamera types defined in mojom
- rename everything to {{module_name}}_ipa_{interface,proxy,proxy_worker}.{c,h}
- remove #include <libcamera/ipa/{{module_name}}.h
- support customizable start()
- remove the need for per-pipeline ControlInfoMap
- add todo for avoiding intermediate vectors
- remove postfix underscore for generated struct fields
- support structs that are members of vectors/maps that aren't defined
  in mojom (in mojom)
- fix has_fd detection
- namespacing is now required in mojom, in the form of ^ipa\.[0-9A-Za-z_]+
- support consts in mojom
- make the pseudo-switch-case in the python generator nicer

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
---
 .../core_ipa_interface.h.tmpl                 |  40 ++
 .../core_ipa_serializer.h.tmpl                |  47 ++
 .../definition_functions.tmpl                 |  53 ++
 .../libcamera_templates/meson.build           |  14 +
 .../module_ipa_interface.h.tmpl               |  87 +++
 .../module_ipa_proxy.cpp.tmpl                 | 236 ++++++++
 .../module_ipa_proxy.h.tmpl                   | 128 +++++
 .../module_ipa_proxy_worker.cpp.tmpl          | 226 ++++++++
 .../module_ipa_serializer.h.tmpl              |  48 ++
 .../libcamera_templates/proxy_functions.tmpl  | 194 +++++++
 .../libcamera_templates/serializer.tmpl       | 313 +++++++++++
 .../generators/mojom_libcamera_generator.py   | 508 ++++++++++++++++++
 12 files changed, 1894 insertions(+)
 create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/definition_functions.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/meson.build
 create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
 create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl
 create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py

Comments

Laurent Pinchart Feb. 12, 2021, 12:25 a.m. UTC | #1
Hi Paul,

Thank you for the patch.

On Thu, Feb 11, 2021 at 04:17:57PM +0900, Paul Elder wrote:
> Add templates to mojo to generate code for the IPC mechanism. These
> templates generate:
> - module header
> - module serializer
> - IPA proxy cpp, header, and worker
> 
> Given an input data definition mojom file for a pipeline.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Acked-by: Jacopo Mondi <jacopo@jmondi.org>
> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> ---
> Changes in v7:
> - cosmetic changes, add a few todos
> - use the new sendSync/sendAsync IPCPipe API
>   - save the sequence number in the proxy
>   - construct the header before calling sendSync/sendAsync (from the
>     proxy)
> - replace genHeader and genSerdes with skipHeader and skipSerdes in
>   core.mojom
> 
> Changes in v6:
> - add templates for core_ipa_interface.h and core_ipa_serializer.h
>   - for libcamera types defined in mojom
> - rename everything to {{module_name}}_ipa_{interface,proxy,proxy_worker}.{c,h}
> - remove #include <libcamera/ipa/{{module_name}}.h
> - support customizable start()
> - remove the need for per-pipeline ControlInfoMap
> - add todo for avoiding intermediate vectors
> - remove postfix underscore for generated struct fields
> - support structs that are members of vectors/maps that aren't defined
>   in mojom (in mojom)
> - fix has_fd detection
> - namespacing is now required in mojom, in the form of ^ipa\.[0-9A-Za-z_]+
> - support consts in mojom
> - make the pseudo-switch-case in the python generator nicer
> 
> 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
> ---
>  .../core_ipa_interface.h.tmpl                 |  40 ++
>  .../core_ipa_serializer.h.tmpl                |  47 ++
>  .../definition_functions.tmpl                 |  53 ++
>  .../libcamera_templates/meson.build           |  14 +
>  .../module_ipa_interface.h.tmpl               |  87 +++
>  .../module_ipa_proxy.cpp.tmpl                 | 236 ++++++++
>  .../module_ipa_proxy.h.tmpl                   | 128 +++++
>  .../module_ipa_proxy_worker.cpp.tmpl          | 226 ++++++++
>  .../module_ipa_serializer.h.tmpl              |  48 ++
>  .../libcamera_templates/proxy_functions.tmpl  | 194 +++++++
>  .../libcamera_templates/serializer.tmpl       | 313 +++++++++++
>  .../generators/mojom_libcamera_generator.py   | 508 ++++++++++++++++++
>  12 files changed, 1894 insertions(+)
>  create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/definition_functions.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/meson.build
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl
>  create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py
> 
> diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> new file mode 100644
> index 00000000..b253881b
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> @@ -0,0 +1,40 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "definition_functions.tmpl" as funcs -%}
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * core_ipa_interface.h - libcamera core definitions for Image Processing Algorithms
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#ifndef __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__
> +#define __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__
> +
> +{% if has_map %}#include <map>{% endif %}
> +{% if has_array %}#include <vector>{% endif %}
> +
> +#include <libcamera/ipa/ipa_interface.h>
> +
> +namespace libcamera {
> +
> +{# \todo Use constexpr instead of const after C++20 for std::string #}

I meant "Use const char * instead of std::string for strings" :-)

Otherwise the patch looks good to me.

> +{% for const in consts %}
> +static const {{const.kind|name}} {{const.mojom_name}} = {{const.value}};
> +{% endfor %}
> +
> +{% for enum in enums %}
> +{{funcs.define_enum(enum)}}
> +{% endfor %}
> +
> +{%- for struct in structs_gen_header %}
> +{{funcs.define_struct(struct)}}
> +{% endfor %}
> +
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__ */
> diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
> new file mode 100644
> index 00000000..37a784f1
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
> @@ -0,0 +1,47 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "serializer.tmpl" as serializer -%}
> +
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * core_ipa_serializer.h - Data serializer for core libcamera definitions for IPA
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__
> +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__
> +
> +#include <tuple>
> +#include <vector>
> +
> +#include <libcamera/ipa/core_ipa_interface.h>
> +
> +#include "libcamera/internal/control_serializer.h"
> +#include "libcamera/internal/ipa_data_serializer.h"
> +
> +namespace libcamera {
> +
> +LOG_DECLARE_CATEGORY(IPADataSerializer)
> +{% for struct in structs_gen_serializer %}
> +template<>
> +class IPADataSerializer<{{struct|name}}>
> +{
> +public:
> +{{- serializer.serializer(struct, "")}}
> +{%- if struct|has_fd %}
> +{{serializer.deserializer_fd(struct, "")}}
> +{%- else %}
> +{{serializer.deserializer_no_fd(struct, "")}}
> +{{serializer.deserializer_fd_simple(struct, "")}}
> +{%- endif %}
> +};
> +{% endfor %}
> +
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__ */
> diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
> new file mode 100644
> index 00000000..cdd75f89
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
> @@ -0,0 +1,53 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +
> +{#
> + # \brief Generate enum definition
> + #
> + # \param enum Enum object whose definition is to be generated
> + #}
> +{%- macro define_enum(enum) -%}
> +enum {{enum.mojom_name}} {
> +{%- for field in enum.fields %}
> +	{{field.mojom_name}} = {{field.numeric_value}},
> +{%- endfor %}
> +};
> +{%- endmacro -%}
> +
> +{#
> + # \brief Generate struct definition
> + #
> + # \param struct Struct object whose definition is to be generated
> + #}
> +{%- macro define_struct(struct) -%}
> +struct {{struct.mojom_name}}
> +{
> +public:
> +	{{struct.mojom_name}}() {%- if struct|has_default_fields %}
> +		:{% endif %}
> +{%- for field in struct.fields|with_default_values -%}
> +{{" " if loop.first}}{{field.mojom_name}}({{field|default_value}}){{", " if not loop.last}}
> +{%- endfor %}
> +	{
> +	}
> +
> +	{{struct.mojom_name}}(
> +{%- for field in struct.fields -%}
> +{{"const " if not field|is_pod}}{{field|name}} {{"&" if not field|is_pod}}_{{field.mojom_name}}{{", " if not loop.last}}
> +{%- endfor -%}
> +)
> +		:
> +{%- for field in struct.fields -%}
> +{{" " if loop.first}}{{field.mojom_name}}(_{{field.mojom_name}}){{", " if not loop.last}}
> +{%- endfor %}
> +	{
> +	}
> +{% for field in struct.fields %}
> +	{{field|name}} {{field.mojom_name}};
> +{%- endfor %}
> +};
> +{%- endmacro -%}
> +
> +
> diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/ipc/generators/libcamera_templates/meson.build
> new file mode 100644
> index 00000000..70664eab
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/meson.build
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +mojom_template_files = files([
> +    'core_ipa_interface.h.tmpl',
> +    'core_ipa_serializer.h.tmpl',
> +    'definition_functions.tmpl',
> +    'module_ipa_interface.h.tmpl',
> +    'module_ipa_proxy.cpp.tmpl',
> +    'module_ipa_proxy.h.tmpl',
> +    'module_ipa_proxy_worker.cpp.tmpl',
> +    'module_ipa_serializer.h.tmpl',
> +    'proxy_functions.tmpl',
> +    'serializer.tmpl',
> +])
> diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
> new file mode 100644
> index 00000000..ebe811fa
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
> @@ -0,0 +1,87 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "definition_functions.tmpl" as funcs -%}
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}}
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#ifndef __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__
> +#define __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__
> +
> +#include <libcamera/ipa/core_ipa_interface.h>
> +#include <libcamera/ipa/ipa_interface.h>
> +
> +{% if has_map %}#include <map>{% endif %}
> +{% if has_array %}#include <vector>{% endif %}
> +
> +namespace libcamera {
> +{%- if has_namespace %}
> +{% for ns in namespace %}
> +namespace {{ns}} {
> +{% endfor %}
> +{%- endif %}
> +
> +{% for const in consts %}
> +const {{const.kind|name}} {{const.mojom_name}} = {{const.value}};
> +{% endfor %}
> +
> +enum class {{cmd_enum_name}} {
> +	Exit = 0,
> +{%- for method in interface_main.methods %}
> +	{{method.mojom_name|cap}} = {{loop.index}},
> +{%- endfor %}
> +};
> +
> +enum class {{cmd_event_enum_name}} {
> +{%- for method in interface_event.methods %}
> +	{{method.mojom_name|cap}} = {{loop.index}},
> +{%- endfor %}
> +};
> +
> +{% for enum in enums %}
> +{{funcs.define_enum(enum)}}
> +{% endfor %}
> +
> +{%- for struct in structs_nonempty %}
> +{{funcs.define_struct(struct)}}
> +{% endfor %}
> +
> +{#-
> +Any consts or #defines should be moved to the mojom file.
> +#}
> +class {{interface_name}} : public IPAInterface
> +{
> +public:
> +{% for method in interface_main.methods %}
> +	virtual {{method|method_return_value}} {{method.mojom_name}}(
> +{%- for param in method|method_parameters %}
> +		{{param}}{{- "," if not loop.last}}
> +{%- endfor -%}
> +) = 0;
> +{% endfor %}
> +
> +{%- for method in interface_event.methods %}
> +	Signal<
> +{%- for param in method.parameters -%}
> +		{{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}}
> +		{{- ", " if not loop.last}}
> +{%- endfor -%}
> +> {{method.mojom_name}};
> +{% endfor -%}
> +};
> +
> +{%- if has_namespace %}
> +{% for ns in namespace|reverse %}
> +} /* namespace {{ns}} */
> +{% endfor %}
> +{%- endif %}
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__ */
> diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> new file mode 100644
> index 00000000..ba34a361
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> @@ -0,0 +1,236 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "proxy_functions.tmpl" as proxy_funcs -%}
> +
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * {{module_name}}_ipa_proxy.cpp - Image Processing Algorithm proxy for {{module_name}}
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#include <libcamera/ipa/{{module_name}}_ipa_proxy.h>
> +
> +#include <memory>
> +#include <string>
> +#include <vector>
> +
> +#include <libcamera/ipa/ipa_module_info.h>
> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
> +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h>
> +
> +#include "libcamera/internal/control_serializer.h"
> +#include "libcamera/internal/ipa_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), seq_(0)
> +{
> +	LOG(IPAProxy, Debug)
> +		<< "initializing {{module_name}} proxy: loading IPA from "
> +		<< ipam->path();
> +
> +	if (isolate_) {
> +		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy");
> +		if (proxyWorkerPath.empty()) {
> +			LOG(IPAProxy, Error)
> +				<< "Failed to get proxy worker path";
> +			return;
> +		}
> +
> +		ipc_ = std::make_unique<IPCPipeUnixSocket>(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_) {
> +		IPCMessage::Header header =
> +			{ static_cast<uint32_t>({{cmd_enum_name}}::Exit), seq_++ };
> +		IPCMessage msg(header);
> +		ipc_->sendAsync(msg);
> +	}
> +}
> +
> +{% if interface_event.methods|length > 0 %}
> +void {{proxy_name}}::recvMessage(const IPCMessage &data)
> +{
> +	size_t dataSize = data.data().size();
> +	{{cmd_event_enum_name}} _cmd = static_cast<{{cmd_event_enum_name}}>(data.header().cmd);
> +
> +	switch (_cmd) {
> +{%- for method in interface_event.methods %}
> +	case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: {
> +		{{method.mojom_name}}IPC(data.data().cbegin(), dataSize, data.fds());
> +		break;
> +	}
> +{%- endfor %}
> +	default:
> +		LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(_cmd);
> +	}
> +}
> +{%- endif %}
> +
> +{% for method in interface_main.methods %}
> +{{proxy_funcs.func_sig(proxy_name, method)}}
> +{
> +	if (isolate_)
> +		{{"return " if method|method_return_value != "void"}}{{method.mojom_name}}IPC(
> +{%- for param in method|method_param_names -%}
> +		{{param}}{{- ", " if not loop.last}}
> +{%- endfor -%}
> +);
> +	else
> +		{{"return " if method|method_return_value != "void"}}{{method.mojom_name}}Thread(
> +{%- for param in method|method_param_names -%}
> +		{{param}}{{- ", " if not loop.last}}
> +{%- endfor -%}
> +);
> +}
> +
> +{{proxy_funcs.func_sig(proxy_name, method, "Thread")}}
> +{
> +{%- if method.mojom_name == "init" %}
> +	{{proxy_funcs.init_thread_body()}}
> +{%- elif method.mojom_name == "stop" %}
> +	{{proxy_funcs.stop_thread_body()}}
> +{%- elif method.mojom_name == "start" %}
> +	running_ = true;
> +	thread_.start();
> +
> +	{{ "return " if method|method_return_value != "void" -}}
> +	proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking
> +	{{- ", " if method|method_param_names}}
> +	{%- for param in method|method_param_names -%}
> +		{{param}}{{- ", " if not loop.last}}
> +	{%- endfor -%}
> +);
> +{%- elif not method|is_async %}
> +	{{ "return " if method|method_return_value != "void" -}}
> +	ipa_->{{method.mojom_name}}(
> +	{%- for param in method|method_param_names -%}
> +		{{param}}{{- ", " if not loop.last}}
> +	{%- endfor -%}
> +);
> +{% elif method|is_async %}
> +	proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued,
> +	{%- for param in method|method_param_names -%}
> +		{{param}}{{- ", " if not loop.last}}
> +	{%- endfor -%}
> +);
> +{%- endif %}
> +}
> +
> +{{proxy_funcs.func_sig(proxy_name, method, "IPC")}}
> +{
> +{%- set has_input = true if method|method_param_inputs|length > 0 %}
> +{%- set has_output = true if method|method_param_outputs|length > 0 or method|method_return_value != "void" %}
> +{%- set cmd = cmd_enum_name + "::" + method.mojom_name|cap %}
> +	IPCMessage::Header _header = { static_cast<uint32_t>({{cmd}}), seq_++ };
> +	IPCMessage _ipcInputBuf(_header);
> +{%- if has_output %}
> +	IPCMessage _ipcOutputBuf;
> +{%- endif %}
> +
> +{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf.data()', '_ipcInputBuf.fds()')}}
> +
> +{% if method|is_async %}
> +	int _ret = ipc_->sendAsync(_ipcInputBuf);
> +{%- else %}
> +	int _ret = ipc_->sendSync(_ipcInputBuf
> +{{- ", &_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<uint8_t>::const_iterator data,
> +	size_t dataSize,
> +	[[maybe_unused]] const std::vector<int32_t> &fds)
> +{
> +{%- for param in method.parameters %}
> +	{{param|name}} {{param.mojom_name}};
> +{%- endfor %}
> +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}}
> +	{{method.mojom_name}}.emit({{method.parameters|params_comma_sep}});
> +}
> +{% endfor %}
> +
> +{%- if has_namespace %}
> +{% for ns in namespace|reverse %}
> +} /* 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..2bc187f2
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> @@ -0,0 +1,128 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "proxy_functions.tmpl" as proxy_funcs -%}
> +
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * {{module_name}}_ipa_proxy.h - Image Processing Algorithm proxy for {{module_name}}
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
> +#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
> +
> +#include <libcamera/ipa/ipa_interface.h>
> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
> +
> +#include "libcamera/internal/control_serializer.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/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(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<uint8_t>::const_iterator data,
> +		size_t dataSize,
> +		const std::vector<int32_t> &fds);
> +{% endfor %}
> +
> +	/* Helper class to invoke async functions in another thread. */
> +	class ThreadProxy : public Object
> +	{
> +	public:
> +		void setIPA({{interface_name}} *ipa)
> +		{
> +			ipa_ = ipa;
> +		}
> +
> +		void stop()
> +		{
> +			ipa_->stop();
> +		}
> +{% for method in interface_main.methods %}
> +{%- if method|is_async %}
> +		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
> +		{
> +			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
> +		}
> +{%- elif method.mojom_name == "start" %}
> +		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
> +		{
> +{%- if method|method_return_value != "void" %}
> +			return ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
> +{%- else %}
> +			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}
> +	{{- ", " if method|method_param_outputs|params_comma_sep -}}
> +	{{- method|method_param_outputs|params_comma_sep}});
> +{%- endif %}
> +		}
> +{%- endif %}
> +{%- endfor %}
> +
> +	private:
> +		{{interface_name}} *ipa_;
> +	};
> +
> +	bool running_;
> +	Thread thread_;
> +	ThreadProxy proxy_;
> +	std::unique_ptr<{{interface_name}}> ipa_;
> +
> +	const bool isolate_;
> +
> +	std::unique_ptr<IPCPipeUnixSocket> ipc_;
> +
> +	ControlSerializer controlSerializer_;
> +
> +	uint32_t seq_;
> +};
> +
> +{%- 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..ac037fa1
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
> @@ -0,0 +1,226 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "proxy_functions.tmpl" as proxy_funcs -%}
> +
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * {{module_name}}_ipa_proxy_worker.cpp - Image Processing Algorithm proxy worker for {{module_name}}
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +{#- \todo Split proxy worker into IPC worker and proxy worker. #}
> +
> +#include <algorithm>
> +#include <iostream>
> +#include <sys/types.h>
> +#include <tuple>
> +#include <unistd.h>
> +
> +#include <libcamera/ipa/ipa_interface.h>
> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
> +#include <libcamera/ipa/{{module_name}}_ipa_serializer.h>
> +#include <libcamera/logging.h>
> +
> +#include "libcamera/internal/camera_sensor.h"
> +#include "libcamera/internal/control_serializer.h"
> +#include "libcamera/internal/event_dispatcher.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/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<uint8_t> _callRetBuf;
> +			std::tie(_callRetBuf, std::ignore) =
> +				IPADataSerializer<{{method|method_return_value}}>::serialize(_callRet);
> +			_response.data().insert(_response.data().end(), _callRetBuf.cbegin(), _callRetBuf.cend());
> +{%- else %}
> +		{{proxy_funcs.serialize_call(method|method_param_outputs, "_response.data()", "_response.fds()")|indent(16, true)}}
> +{%- endif %}
> +			int _ret = socket_.send(_response.payload());
> +			if (_ret < 0) {
> +				LOG({{proxy_worker_name}}, Error)
> +					<< "Reply to {{method.mojom_name}}() failed: " << _ret;
> +			}
> +			LOG({{proxy_worker_name}}, Debug) << "Done replying to {{method.mojom_name}}()";
> +{%- endif %}
> +			break;
> +		}
> +{% endfor %}
> +		default:
> +			LOG({{proxy_worker_name}}, Error) << "Unknown command " << _ipcMessage.header().cmd;
> +		}
> +	}
> +
> +	int init(std::unique_ptr<IPAModule> &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<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}),
> +			0
> +		};
> +		IPCMessage _message(header);
> +
> +		{{proxy_funcs.serialize_call(method|method_param_inputs, "_message.data()", "_message.fds()")}}
> +
> +		socket_.send(_message.payload());
> +
> +		LOG({{proxy_worker_name}}, Debug) << "{{method.mojom_name}} done";
> +	}
> +{% endfor %}
> +
> +	{{interface_name}} *ipa_;
> +	IPCUnixSocket socket_;
> +
> +	ControlSerializer controlSerializer_;
> +
> +	bool exit_;
> +};
> +
> +int main(int argc, char **argv)
> +{
> +{#- \todo Handle enabling debugging more dynamically. #}
> +	/* Uncomment this for debugging. */
> +#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 <path to IPA so> <fd to bind unix socket>";
> +		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<IPAModule> ipam = std::make_unique<IPAModule>(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 initialized";
> +
> +	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..64ae99dc
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
> @@ -0,0 +1,48 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{%- import "serializer.tmpl" as serializer -%}
> +
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * {{module_name}}_ipa_serializer.h - Image Processing Algorithm data serializer for {{module_name}}
> + *
> + * This file is auto-generated. Do not edit.
> + */
> +
> +#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__
> +#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__
> +
> +#include <tuple>
> +#include <vector>
> +
> +#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
> +#include <libcamera/ipa/core_ipa_serializer.h>
> +
> +#include "libcamera/internal/control_serializer.h"
> +#include "libcamera/internal/ipa_data_serializer.h"
> +
> +namespace libcamera {
> +
> +LOG_DECLARE_CATEGORY(IPADataSerializer)
> +{% for struct in structs_nonempty %}
> +template<>
> +class IPADataSerializer<{{struct|name_full(namespace_str)}}>
> +{
> +public:
> +{{- serializer.serializer(struct, namespace_str)}}
> +{%- if struct|has_fd %}
> +{{serializer.deserializer_fd(struct, namespace_str)}}
> +{%- else %}
> +{{serializer.deserializer_no_fd(struct, namespace_str)}}
> +{{serializer.deserializer_fd_simple(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..40611feb
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
> @@ -0,0 +1,194 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{#
> + # \brief Generate function prototype
> + #
> + # \param class Class name
> + # \param method mojom Method object
> + # \param suffix Suffix to append to \a method function name
> + # \param need_class_name If true, generate class name with function
> + # \param override If true, generate override tag after the function prototype
> + #}
> +{%- macro func_sig(class, method, suffix = "", need_class_name = true, override = false) -%}
> +{{method|method_return_value}} {{class + "::" if need_class_name}}{{method.mojom_name}}{{suffix}}(
> +{%- for param in method|method_parameters %}
> +	{{param}}{{- "," if not loop.last}}
> +{%- endfor -%}
> +){{" override" if override}}
> +{%- endmacro -%}
> +
> +{#
> + # \brief Generate function body for IPA init() function for thread
> + #}
> +{%- macro init_thread_body() -%}
> +	int ret = ipa_->init(settings);
> +	if (ret)
> +		return ret;
> +
> +	proxy_.moveToThread(&thread_);
> +
> +	return 0;
> +{%- endmacro -%}
> +
> +{#
> + # \brief Generate function body for IPA stop() function for thread
> + #}
> +{%- macro stop_thread_body() -%}
> +	if (!running_)
> +		return;
> +
> +	running_ = false;
> +
> +	proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking);
> +
> +	thread_.exit();
> +	thread_.wait();
> +{%- endmacro -%}
> +
> +
> +{#
> + # \brief Serialize multiple objects into data buffer and fd vector
> + #
> + # Generate code to serialize multiple objects, as specified in \a params
> + # (which are the parameters to some function), into \a buf data buffer and
> + # \a fds fd vector.
> + # This code is meant to be used by the proxy, for serializing prior to IPC calls.
> + #
> + # \todo Avoid intermediate vectors
> + #}
> +{%- macro serialize_call(params, buf, fds) %}
> +{%- for param in params %}
> +	std::vector<uint8_t> {{param.mojom_name}}Buf;
> +{%- if param|has_fd %}
> +	std::vector<int32_t> {{param.mojom_name}}Fds;
> +	std::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) =
> +{%- else %}
> +	std::tie({{param.mojom_name}}Buf, std::ignore) =
> +{%- endif %}
> +		IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}
> +{{- ", &controlSerializer_" if param|needs_control_serializer -}}
> +);
> +{%- endfor %}
> +
> +{%- if params|length > 1 %}
> +{%- for param in params %}
> +	appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size());
> +{%- if param|has_fd %}
> +	appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Fds.size());
> +{%- endif %}
> +{%- endfor %}
> +{%- endif %}
> +
> +{%- for param in params %}
> +	{{buf}}.insert({{buf}}.end(), {{param.mojom_name}}Buf.begin(), {{param.mojom_name}}Buf.end());
> +{%- endfor %}
> +
> +{%- for param in params %}
> +{%- if param|has_fd %}
> +	{{fds}}.insert({{fds}}.end(), {{param.mojom_name}}Fds.begin(), {{param.mojom_name}}Fds.end());
> +{%- endif %}
> +{%- endfor %}
> +{%- endmacro -%}
> +
> +
> +{#
> + # \brief Deserialize a single object from data buffer and fd vector
> + #
> + # \param pointer If true, deserialize the object into a dereferenced pointer
> + # \param iter If true, treat \a buf as an iterator instead of a vector
> + # \param data_size Variable that holds the size of the vector referenced by \a buf
> + #
> + # Generate code to deserialize a single object, as specified in \a param,
> + # from \a buf data buffer and \a fds fd vector.
> + # This code is meant to be used by macro deserialize_call.
> + #}
> +{%- macro deserialize_param(param, pointer, loop, buf, fds, iter, data_size) -%}
> +{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize(
> +	{{buf}}{{- ".cbegin()" if not iter}} + {{param.mojom_name}}Start,
> +{%- if loop.last and not iter %}
> +	{{buf}}.cend()
> +{%- elif not iter %}
> +	{{buf}}.cbegin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize
> +{%- elif iter and loop.length == 1 %}
> +	{{buf}} + {{data_size}}
> +{%- else %}
> +	{{buf}} + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize
> +{%- endif -%}
> +{{- "," if param|has_fd}}
> +{%- if param|has_fd %}
> +	{{fds}}.cbegin() + {{param.mojom_name}}FdStart,
> +{%- if loop.last %}
> +	{{fds}}.cend()
> +{%- else %}
> +	{{fds}}.cbegin() + {{param.mojom_name}}FdStart + {{param.mojom_name}}FdsSize
> +{%- endif -%}
> +{%- endif -%}
> +{{- "," if param|needs_control_serializer}}
> +{%- if param|needs_control_serializer %}
> +	&controlSerializer_
> +{%- endif -%}
> +);
> +{%- endmacro -%}
> +
> +
> +{#
> + # \brief Deserialize multiple objects from data buffer and fd vector
> + #
> + # \param pointer If true, deserialize objects into pointers, and adds a null check.
> + # \param declare If true, declare the objects in addition to deserialization.
> + # \param iter if true, treat \a buf as an iterator instead of a vector
> + # \param data_size Variable that holds the size of the vector referenced by \a buf
> + #
> + # Generate code to deserialize multiple objects, as specified in \a params
> + # (which are the parameters to some function), from \a buf data buffer and
> + # \a fds fd vector.
> + # This code is meant to be used by the proxy, for deserializing after IPC calls.
> + #
> + # \todo Avoid intermediate vectors
> + #}
> +{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%}
> +{% set ns = namespace(size_offset = 0) %}
> +{%- if params|length > 1 %}
> +{%- for param in params %}
> +	[[maybe_unused]] const size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}}
> +{%- if iter -%}
> +, {{buf}} + {{data_size}}
> +{%- endif -%}
> +);
> +	{%- set ns.size_offset = ns.size_offset + 4 %}
> +{%- if param|has_fd %}
> +	[[maybe_unused]] const size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}}
> +{%- if iter -%}
> +, {{buf}} + {{data_size}}
> +{%- endif -%}
> +);
> +	{%- set ns.size_offset = ns.size_offset + 4 %}
> +{%- endif %}
> +{%- endfor %}
> +{%- endif %}
> +{% for param in params %}
> +{%- if loop.first %}
> +	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..af35b9e3
> --- /dev/null
> +++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl
> @@ -0,0 +1,313 @@
> +{#-
> + # SPDX-License-Identifier: LGPL-2.1-or-later
> + # Copyright (C) 2020, Google Inc.
> +-#}
> +{#
> + # \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 a field into return vector
> + #
> + # Generate code to serialize \a field into retData, including size of the
> + # field and fds (where appropriate).
> + # This code is meant to be used by the IPADataSerializer specialization.
> + #
> + # \todo Avoid intermediate vectors
> + #}
> +{%- macro serializer_field(field, namespace, loop) %}
> +{%- if field|is_pod or field|is_enum %}
> +		std::vector<uint8_t> {{field.mojom_name}};
> +		std::tie({{field.mojom_name}}, std::ignore) =
> +	{%- if field|is_pod %}
> +			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
> +	{%- elif field|is_enum %}
> +			IPADataSerializer<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}});
> +	{%- endif %}
> +		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
> +{%- elif field|is_fd %}
> +		std::vector<uint8_t> {{field.mojom_name}};
> +		std::vector<int32_t> {{field.mojom_name}}Fds;
> +		std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =
> +			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
> +		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
> +		retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());
> +{%- elif field|is_controls %}
> +		if (data.{{field.mojom_name}}.size() > 0) {
> +			std::vector<uint8_t> {{field.mojom_name}};
> +			std::tie({{field.mojom_name}}, std::ignore) =
> +				IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}, cs);
> +			appendPOD<uint32_t>(retData, {{field.mojom_name}}.size());
> +			retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
> +		} else {
> +			appendPOD<uint32_t>(retData, 0);
> +		}
> +{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %}
> +		std::vector<uint8_t> {{field.mojom_name}};
> +	{%- if field|has_fd %}
> +		std::vector<int32_t> {{field.mojom_name}}Fds;
> +		std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =
> +	{%- else %}
> +		std::tie({{field.mojom_name}}, std::ignore) =
> +	{%- endif %}
> +	{%- if field|is_array or field|is_map %}
> +			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}, cs);
> +	{%- elif field|is_str %}
> +			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
> +	{%- else %}
> +			IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}, cs);
> +	{%- endif %}
> +		appendPOD<uint32_t>(retData, {{field.mojom_name}}.size());
> +	{%- if field|has_fd %}
> +		appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size());
> +	{%- endif %}
> +		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
> +	{%- if field|has_fd %}
> +		retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());
> +	{%- endif %}
> +{%- else %}
> +		/* Unknown serialization for {{field.mojom_name}}. */
> +{%- endif %}
> +{%- endmacro %}
> +
> +
> +{#
> + # \brief Deserialize a field into return struct
> + #
> + # Generate code to deserialize \a field into object ret.
> + # This code is meant to be used by the IPADataSerializer specialization.
> + #}
> +{%- macro deserializer_field(field, namespace, loop) %}
> +{% if field|is_pod or field|is_enum %}
> +	{%- set field_size = (field|bit_width|int / 8)|int %}
> +		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
> +		{%- if field|is_pod %}
> +		ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field_size}});
> +		{%- else %}
> +		ret.{{field.mojom_name}} = static_cast<{{field|name_full(namespace)}}>(IPADataSerializer<uint{{field|bit_width}}_t>::deserialize(m, m + {{field_size}}));
> +		{%- endif %}
> +	{%- if not loop.last %}
> +		m += {{field_size}};
> +		dataSize -= {{field_size}};
> +	{%- endif %}
> +{% elif field|is_fd %}
> +	{%- set field_size = 1 %}
> +		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
> +		ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + 1, n, n + 1, cs);
> +	{%- if not loop.last %}
> +		m += {{field_size}};
> +		dataSize -= {{field_size}};
> +		n += ret.{{field.mojom_name}}.isValid() ? 1 : 0;
> +		fdsSize -= ret.{{field.mojom_name}}.isValid() ? 1 : 0;
> +	{%- endif %}
> +{% elif field|is_controls %}
> +	{%- set field_size = 4 %}
> +		{{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}}
> +		const size_t {{field.mojom_name}}Size = readPOD<uint32_t>(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<uint32_t>(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<uint32_t>(m, 0, dataEnd);
> +		m += {{field_size}};
> +		dataSize -= {{field_size}};
> +		{{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds')}}
> +	{%- endif %}
> +	{%- set field_size = field.mojom_name + 'Size' -%}
> +		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
> +		ret.{{field.mojom_name}} =
> +	{%- if field|is_str %}
> +			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size);
> +	{%- elif field|has_fd and (field|is_array or field|is_map) %}
> +			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs);
> +	{%- elif field|has_fd and (not (field|is_array or field|is_map)) %}
> +			IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m, m + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs);
> +	{%- elif (not field|has_fd) and (field|is_array or field|is_map) %}
> +			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size, cs);
> +	{%- else %}
> +			IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m, m + {{field.mojom_name}}Size, cs);
> +	{%- endif %}
> +	{%- if not loop.last %}
> +		m += {{field_size}};
> +		dataSize -= {{field_size}};
> +	{%- if field|has_fd %}
> +		n += {{field.mojom_name}}FdsSize;
> +		fdsSize -= {{field.mojom_name}}FdsSize;
> +	{%- endif %}
> +	{%- endif %}
> +{% else %}
> +		/* Unknown deserialization for {{field.mojom_name}}. */
> +{%- endif %}
> +{%- endmacro %}
> +
> +
> +{#
> + # \brief Serialize a struct
> + #
> + # Generate code for IPADataSerializer specialization, for serializing
> + # \a struct.
> + #}
> +{%- macro serializer(struct, namespace) %}
> +	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
> +	serialize(const {{struct|name_full(namespace)}} &data,
> +{%- if struct|needs_control_serializer %}
> +		  ControlSerializer *cs)
> +{%- else %}
> +		  [[maybe_unused]] ControlSerializer *cs = nullptr)
> +{%- endif %}
> +	{
> +		std::vector<uint8_t> retData;
> +{%- if struct|has_fd %}
> +		std::vector<int32_t> retFds;
> +{%- endif %}
> +{%- for field in struct.fields %}
> +{{serializer_field(field, namespace, loop)}}
> +{%- endfor %}
> +{% if struct|has_fd %}
> +		return {retData, retFds};
> +{%- else %}
> +		return {retData, {}};
> +{%- endif %}
> +	}
> +{%- endmacro %}
> +
> +
> +{#
> + # \brief Deserialize a struct that has fds
> + #
> + # Generate code for IPADataSerializer specialization, for deserializing
> + # \a struct, in the case that \a struct has file descriptors.
> + #}
> +{%- macro deserializer_fd(struct, namespace) %}
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t> &data,
> +		    std::vector<int32_t> &fds,
> +{%- if struct|needs_control_serializer %}
> +		    ControlSerializer *cs)
> +{%- else %}
> +		    ControlSerializer *cs = nullptr)
> +{%- endif %}
> +	{
> +		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), fds.cbegin(), fds.cend(), cs);
> +	}
> +
> +{# \todo Don't inline this function #}
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> +		    std::vector<uint8_t>::const_iterator dataEnd,
> +		    std::vector<int32_t>::const_iterator fdsBegin,
> +		    std::vector<int32_t>::const_iterator fdsEnd,
> +{%- if struct|needs_control_serializer %}
> +		    ControlSerializer *cs)
> +{%- else %}
> +		    [[maybe_unused]] ControlSerializer *cs = nullptr)
> +{%- endif %}
> +	{
> +		{{struct|name_full(namespace)}} ret;
> +		std::vector<uint8_t>::const_iterator m = dataBegin;
> +		std::vector<int32_t>::const_iterator n = fdsBegin;
> +
> +		size_t dataSize = std::distance(dataBegin, dataEnd);
> +		[[maybe_unused]] size_t fdsSize = std::distance(fdsBegin, fdsEnd);
> +{%- for field in struct.fields -%}
> +{{deserializer_field(field, namespace, loop)}}
> +{%- endfor %}
> +		return ret;
> +	}
> +{%- endmacro %}
> +
> +{#
> + # \brief Deserialize a struct that has fds, using non-fd
> + #
> + # Generate code for IPADataSerializer specialization, for deserializing
> + # \a struct, in the case that \a struct has no file descriptors but requires
> + # deserializers with file descriptors.
> + #}
> +{%- macro deserializer_fd_simple(struct, namespace) %}
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t> &data,
> +		    [[maybe_unused]] std::vector<int32_t> &fds,
> +		    ControlSerializer *cs = nullptr)
> +	{
> +		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs);
> +	}
> +
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> +		    std::vector<uint8_t>::const_iterator dataEnd,
> +		    [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin,
> +		    [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
> +		    ControlSerializer *cs = nullptr)
> +	{
> +		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(dataBegin, dataEnd, cs);
> +	}
> +{%- endmacro %}
> +
> +
> +{#
> + # \brief Deserialize a struct that has no fds
> + #
> + # Generate code for IPADataSerializer specialization, for deserializing
> + # \a struct, in the case that \a struct does not have file descriptors.
> + #}
> +{%- macro deserializer_no_fd(struct, namespace) %}
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t> &data,
> +{%- if struct|needs_control_serializer %}
> +		    ControlSerializer *cs)
> +{%- else %}
> +		    ControlSerializer *cs = nullptr)
> +{%- endif %}
> +	{
> +		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs);
> +	}
> +
> +{# \todo Don't inline this function #}
> +	static {{struct|name_full(namespace)}}
> +	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> +		    std::vector<uint8_t>::const_iterator dataEnd,
> +{%- if struct|needs_control_serializer %}
> +		    ControlSerializer *cs)
> +{%- else %}
> +		    [[maybe_unused]] ControlSerializer *cs = nullptr)
> +{%- endif %}
> +	{
> +		{{struct|name_full(namespace)}} ret;
> +		std::vector<uint8_t>::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..438e41c6
> --- /dev/null
> +++ b/utils/ipc/generators/mojom_libcamera_generator.py
> @@ -0,0 +1,508 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (C) 2020, Google Inc.
> +#
> +# Author: Paul Elder <paul.elder@ideasonboard.com>
> +#
> +# mojom_libcamera_generator.py - Generates libcamera files from a mojom.Module.
> +
> +import argparse
> +import datetime
> +import os
> +import re
> +
> +import mojom.fileutil as fileutil
> +import mojom.generate.generator as generator
> +import mojom.generate.module as mojom
> +from mojom.generate.template_expander import UseJinja
> +
> +
> +GENERATOR_PREFIX = 'libcamera'
> +
> +_kind_to_cpp_type = {
> +    mojom.BOOL:   'bool',
> +    mojom.INT8:   'int8_t',
> +    mojom.UINT8:  'uint8_t',
> +    mojom.INT16:  'int16_t',
> +    mojom.UINT16: 'uint16_t',
> +    mojom.INT32:  'int32_t',
> +    mojom.UINT32: 'uint32_t',
> +    mojom.FLOAT:  'float',
> +    mojom.INT64:  'int64_t',
> +    mojom.UINT64: 'uint64_t',
> +    mojom.DOUBLE: 'double',
> +}
> +
> +_bit_widths = {
> +    mojom.BOOL:   '8',
> +    mojom.INT8:   '8',
> +    mojom.UINT8:  '8',
> +    mojom.INT16:  '16',
> +    mojom.UINT16: '16',
> +    mojom.INT32:  '32',
> +    mojom.UINT32: '32',
> +    mojom.FLOAT:  '32',
> +    mojom.INT64:  '64',
> +    mojom.UINT64: '64',
> +    mojom.DOUBLE: '64',
> +}
> +
> +def ModuleName(path):
> +    return path.split('/')[-1].split('.')[0]
> +
> +def ModuleClassName(module):
> +    return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1),
> +                  module.interfaces[0].mojom_name)
> +
> +def Capitalize(name):
> +    return name[0].upper() + name[1:]
> +
> +def ConstantStyle(name):
> +    return generator.ToUpperSnakeCase(name)
> +
> +def Choose(cond, t, f):
> +    return t if cond else f
> +
> +def CommaSep(l):
> +    return ', '.join([m for m in l])
> +
> +def ParamsCommaSep(l):
> +    return ', '.join([m.mojom_name for m in l])
> +
> +def GetDefaultValue(element):
> +    if element.default is not None:
> +        return element.default
> +    if type(element.kind) == mojom.Kind:
> +        return '0'
> +    if mojom.IsEnumKind(element.kind):
> +        return f'static_cast<{element.kind.mojom_name}>(0)'
> +    if isinstance(element.kind, mojom.Struct) and \
> +       element.kind.mojom_name == 'FileDescriptor':
> +        return '-1'
> +    return ''
> +
> +def HasDefaultValue(element):
> +    return GetDefaultValue(element) != ''
> +
> +def HasDefaultFields(element):
> +    return True in [HasDefaultValue(x) for x in element.fields]
> +
> +def GetAllTypes(element):
> +    if mojom.IsArrayKind(element):
> +        return GetAllTypes(element.kind)
> +    if mojom.IsMapKind(element):
> +        return GetAllTypes(element.key_kind) + GetAllTypes(element.value_kind)
> +    if isinstance(element, mojom.Parameter):
> +        return GetAllTypes(element.kind)
> +    if mojom.IsEnumKind(element):
> +        return [element.mojom_name]
> +    if not mojom.IsStructKind(element):
> +        return [element.spec]
> +    if len(element.fields) == 0:
> +        return [element.mojom_name]
> +    ret = [GetAllTypes(x.kind) for x in element.fields]
> +    ret = [x for sublist in ret for x in sublist]
> +    return list(set(ret))
> +
> +def GetAllAttrs(element):
> +    if mojom.IsArrayKind(element):
> +        return GetAllAttrs(element.kind)
> +    if mojom.IsMapKind(element):
> +        return {**GetAllAttrs(element.key_kind), **GetAllAttrs(element.value_kind)}
> +    if isinstance(element, mojom.Parameter):
> +        return GetAllAttrs(element.kind)
> +    if mojom.IsEnumKind(element):
> +        return element.attributes if element.attributes is not None else {}
> +    if mojom.IsStructKind(element) and len(element.fields) == 0:
> +        return element.attributes if element.attributes is not None else {}
> +    if not mojom.IsStructKind(element):
> +        if hasattr(element, 'attributes'):
> +            return element.attributes or {}
> +        return {}
> +    attrs = [(x.attributes) for x in element.fields]
> +    ret = {}
> +    for d in attrs:
> +        ret.update(d or {})
> +    if hasattr(element, 'attributes'):
> +        ret.update(element.attributes or {})
> +    return ret
> +
> +def NeedsControlSerializer(element):
> +    types = GetAllTypes(element)
> +    return "ControlList" in types or "ControlInfoMap" in types
> +
> +def HasFd(element):
> +    attrs = GetAllAttrs(element)
> +    if isinstance(element, mojom.Kind):
> +        types = GetAllTypes(element)
> +    else:
> +        types = GetAllTypes(element.kind)
> +    return "FileDescriptor" in types or (attrs is not None and "hasFd" in attrs)
> +
> +def WithDefaultValues(element):
> +    return [x for x in element if HasDefaultValue(x)]
> +
> +def WithFds(element):
> +    return [x for x in element if HasFd(x)]
> +
> +def MethodParamInputs(method):
> +    return method.parameters
> +
> +def MethodParamOutputs(method):
> +    if (MethodReturnValue(method) != 'void' or
> +        method.response_parameters is None):
> +        return []
> +    return method.response_parameters
> +
> +def MethodParamsHaveFd(parameters):
> +    return len([x for x in parameters if HasFd(x)]) > 0
> +
> +def MethodInputHasFd(method):
> +    return MethodParamsHaveFd(method.parameters)
> +
> +def MethodOutputHasFd(method):
> +    return MethodParamsHaveFd(MethodParamOutputs(method))
> +
> +def MethodParamNames(method):
> +    params = []
> +    for param in method.parameters:
> +        params.append(param.mojom_name)
> +    if MethodReturnValue(method) == 'void':
> +        if method.response_parameters is None:
> +            return params
> +        for param in method.response_parameters:
> +            params.append(param.mojom_name)
> +    return params
> +
> +def MethodParameters(method):
> +    params = []
> +    for param in method.parameters:
> +        params.append('const %s %s%s' % (GetNameForElement(param),
> +                                         '&' if not IsPod(param) else '',
> +                                         param.mojom_name))
> +    if MethodReturnValue(method) == 'void':
> +        if method.response_parameters is None:
> +            return params
> +        for param in method.response_parameters:
> +            params.append(f'{GetNameForElement(param)} *{param.mojom_name}')
> +    return params
> +
> +def MethodReturnValue(method):
> +    if method.response_parameters is None:
> +        return 'void'
> +    if len(method.response_parameters) == 1 and IsPod(method.response_parameters[0]):
> +        return GetNameForElement(method.response_parameters[0])
> +    return 'void'
> +
> +def IsAsync(method):
> +    # Events are always async
> +    if re.match("^IPA.*EventInterface$", method.interface.mojom_name):
> +        return True
> +    elif re.match("^IPA.*Interface$", method.interface.mojom_name):
> +        if method.attributes is None:
> +            return False
> +        elif 'async' in method.attributes and method.attributes['async']:
> +            return True
> +    return False
> +
> +def IsArray(element):
> +    return mojom.IsArrayKind(element.kind)
> +
> +def IsControls(element):
> +    return mojom.IsStructKind(element.kind) and (element.kind.mojom_name == "ControlList" or
> +                                                 element.kind.mojom_name == "ControlInfoMap")
> +
> +def IsEnum(element):
> +    return mojom.IsEnumKind(element.kind)
> +
> +def IsFd(element):
> +    return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "FileDescriptor"
> +
> +def IsMap(element):
> +    return mojom.IsMapKind(element.kind)
> +
> +def IsPlainStruct(element):
> +    return mojom.IsStructKind(element.kind) and not IsControls(element) and not IsFd(element)
> +
> +def IsPod(element):
> +    return element.kind in _kind_to_cpp_type
> +
> +def IsStr(element):
> +    return element.kind.spec == 's'
> +
> +def BitWidth(element):
> +    if element.kind in _bit_widths:
> +        return _bit_widths[element.kind]
> +    if mojom.IsEnumKind(element.kind):
> +        return '32'
> +    return ''
> +
> +# Get the type name for a given element
> +def GetNameForElement(element):
> +    # structs
> +    if (mojom.IsEnumKind(element) or
> +        mojom.IsInterfaceKind(element) or
> +        mojom.IsStructKind(element)):
> +        return element.mojom_name
> +    # vectors
> +    if (mojom.IsArrayKind(element)):
> +        elem_name = GetNameForElement(element.kind)
> +        return f'std::vector<{elem_name}>'
> +    # maps
> +    if (mojom.IsMapKind(element)):
> +        key_name = GetNameForElement(element.key_kind)
> +        value_name = GetNameForElement(element.value_kind)
> +        return f'std::map<{key_name}, {value_name}>'
> +    # struct fields and function parameters
> +    if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)):
> +        # maps and vectors
> +        if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)):
> +            return GetNameForElement(element.kind)
> +        # strings
> +        if (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'):
> +            return 'std::string'
> +        # PODs
> +        if element.kind in _kind_to_cpp_type:
> +            return _kind_to_cpp_type[element.kind]
> +        # structs and enums
> +        return element.kind.mojom_name
> +    # PODs that are members of vectors/maps
> +    if (hasattr(element, '__hash__') and element in _kind_to_cpp_type):
> +        return _kind_to_cpp_type[element]
> +    if (hasattr(element, 'spec')):
> +        # strings that are members of vectors/maps
> +        if (element.spec == 's'):
> +            return 'std::string'
> +        # structs that aren't defined in mojom that are members of vectors/maps
> +        if (element.spec[0] == 'x'):
> +            return element.spec.replace('x:', '').replace('.', '::')
> +    if (mojom.IsInterfaceRequestKind(element) or
> +        mojom.IsAssociatedKind(element) or
> +        mojom.IsPendingRemoteKind(element) or
> +        mojom.IsPendingReceiverKind(element) or
> +        mojom.IsUnionKind(element)):
> +        raise Exception('Unsupported element: %s' % element)
> +    raise Exception('Unexpected element: %s' % element)
> +
> +def GetFullNameForElement(element, namespace_str):
> +    name = GetNameForElement(element)
> +    if namespace_str == '':
> +        return name
> +    return f'{namespace_str}::{name}'
> +
> +def ValidateZeroLength(l, s, cap=True):
> +    if l is None:
> +        return
> +    if len(l) > 0:
> +        raise Exception(f'{s.capitalize() if cap else s} should be empty')
> +
> +def ValidateSingleLength(l, s, cap=True):
> +    if len(l) > 1:
> +        raise Exception(f'Only one {s} allowed')
> +    if len(l) < 1:
> +        raise Exception(f'{s.capitalize() if cap else s} is required')
> +
> +def GetMainInterface(interfaces):
> +    intf = [x for x in interfaces
> +            if re.match("^IPA.*Interface", x.mojom_name) and
> +               not re.match("^IPA.*EventInterface", x.mojom_name)]
> +    ValidateSingleLength(intf, 'main interface')
> +    return None if len(intf) == 0 else intf[0]
> +
> +def GetEventInterface(interfaces):
> +    event = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)]
> +    ValidateSingleLength(event, 'event interface')
> +    return None if len(event) == 0 else event[0]
> +
> +def ValidateNamespace(namespace):
> +    if namespace == '':
> +        raise Exception('Must have a namespace')
> +
> +    if not re.match('^ipa\.[0-9A-Za-z_]+', namespace):
> +        raise Exception('Namespace must be of the form "ipa.{pipeline_name}"')
> +
> +def ValidateInterfaces(interfaces):
> +    # Validate presence of main interface
> +    intf = GetMainInterface(interfaces)
> +    if intf is None:
> +        raise Exception('Must have main IPA interface')
> +
> +    # Validate presence of event interface
> +    event = GetEventInterface(interfaces)
> +    if intf is None:
> +        raise Exception('Must have event IPA interface')
> +
> +    # Validate required main interface functions
> +    f_init  = [x for x in intf.methods if x.mojom_name == 'init']
> +    f_start = [x for x in intf.methods if x.mojom_name == 'start']
> +    f_stop  = [x for x in intf.methods if x.mojom_name == 'stop']
> +
> +    ValidateSingleLength(f_init, 'init()', False)
> +    ValidateSingleLength(f_start, 'start()', False)
> +    ValidateSingleLength(f_stop, 'stop()', False)
> +
> +    f_init  = f_init[0]
> +    f_start = f_start[0]
> +    f_stop  = f_stop[0]
> +
> +    # Validate parameters to init()
> +    ValidateSingleLength(f_init.parameters, 'input parameter to init()')
> +    ValidateSingleLength(f_init.response_parameters, 'output parameter from init()')
> +    if f_init.parameters[0].kind.mojom_name != 'IPASettings':
> +        raise Exception('init() must have single IPASettings input parameter')
> +    if f_init.response_parameters[0].kind.spec != 'i32':
> +        raise Exception('init() must have single int32 output parameter')
> +
> +    # No need to validate start() as it is customizable
> +
> +    # Validate parameters to stop()
> +    ValidateZeroLength(f_stop.parameters, 'input parameter to stop()')
> +    ValidateZeroLength(f_stop.parameters, 'output parameter from stop()')
> +
> +    # Validate that event interface has at least one event
> +    if len(event.methods) < 1:
> +        raise Exception('Event interface must have at least one event')
> +
> +    # Validate that all async methods don't have return values
> +    intf_methods_async = [x for x in intf.methods if IsAsync(x)]
> +    for method in intf_methods_async:
> +        ValidateZeroLength(method.response_parameters,
> +                           f'{method.mojom_name} response parameters', False)
> +
> +    event_methods_async = [x for x in event.methods if IsAsync(x)]
> +    for method in event_methods_async:
> +        ValidateZeroLength(method.response_parameters,
> +                           f'{method.mojom_name} response parameters', False)
> +
> +class Generator(generator.Generator):
> +    @staticmethod
> +    def GetTemplatePrefix():
> +        return 'libcamera_templates'
> +
> +    def GetFilters(self):
> +        libcamera_filters = {
> +            'all_types': GetAllTypes,
> +            'bit_width': BitWidth,
> +            'cap': Capitalize,
> +            'choose': Choose,
> +            'comma_sep': CommaSep,
> +            'default_value': GetDefaultValue,
> +            'has_default_fields': HasDefaultFields,
> +            'has_fd': HasFd,
> +            'is_async': IsAsync,
> +            'is_array': IsArray,
> +            'is_controls': IsControls,
> +            'is_enum': IsEnum,
> +            'is_fd': IsFd,
> +            'is_map': IsMap,
> +            'is_plain_struct': IsPlainStruct,
> +            'is_pod': IsPod,
> +            'is_str': IsStr,
> +            'method_input_has_fd': MethodInputHasFd,
> +            'method_output_has_fd': MethodOutputHasFd,
> +            'method_param_names': MethodParamNames,
> +            'method_param_inputs': MethodParamInputs,
> +            'method_param_outputs': MethodParamOutputs,
> +            'method_parameters': MethodParameters,
> +            'method_return_value': MethodReturnValue,
> +            'name': GetNameForElement,
> +            'name_full': GetFullNameForElement,
> +            'needs_control_serializer': NeedsControlSerializer,
> +            'params_comma_sep': ParamsCommaSep,
> +            'with_default_values': WithDefaultValues,
> +            'with_fds': WithFds,
> +        }
> +        return libcamera_filters
> +
> +    def _GetJinjaExports(self):
> +        return {
> +            'cmd_enum_name': '_%sCmd' % self.module_name,
> +            'cmd_event_enum_name': '_%sEventCmd' % self.module_name,
> +            'consts': self.module.constants,
> +            'enums': self.module.enums,
> +            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
> +            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
> +            'has_namespace': self.module.mojom_namespace != '',
> +            'interface_event': GetEventInterface(self.module.interfaces),
> +            'interface_main': GetMainInterface(self.module.interfaces),
> +            'interface_name': 'IPA%sInterface' % self.module_name,
> +            'module_name': ModuleName(self.module.path),
> +            'namespace': self.module.mojom_namespace.split('.'),
> +            'namespace_str': self.module.mojom_namespace.replace('.', '::') if
> +                             self.module.mojom_namespace is not None else '',
> +            'proxy_name': 'IPAProxy%s' % self.module_name,
> +            'proxy_worker_name': 'IPAProxy%sWorker' % self.module_name,
> +            'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0],
> +        }
> +
> +    def _GetJinjaExportsForCore(self):
> +        return {
> +            'consts': self.module.constants,
> +            'enums': self.module.enums,
> +            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
> +            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
> +            'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes],
> +            'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes],
> +        }
> +
> +    @UseJinja('core_ipa_interface.h.tmpl')
> +    def _GenerateCoreHeader(self):
> +        return self._GetJinjaExportsForCore()
> +
> +    @UseJinja('core_ipa_serializer.h.tmpl')
> +    def _GenerateCoreSerializer(self):
> +        return self._GetJinjaExportsForCore()
> +
> +    @UseJinja('module_ipa_interface.h.tmpl')
> +    def _GenerateDataHeader(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_serializer.h.tmpl')
> +    def _GenerateSerializer(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy.cpp.tmpl')
> +    def _GenerateProxyCpp(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy.h.tmpl')
> +    def _GenerateProxyHeader(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy_worker.cpp.tmpl')
> +    def _GenerateProxyWorker(self):
> +        return self._GetJinjaExports()
> +
> +    def GenerateFiles(self, unparsed_args):
> +        parser = argparse.ArgumentParser()
> +        parser.add_argument('--libcamera_generate_core_header',     action='store_true')
> +        parser.add_argument('--libcamera_generate_core_serializer', action='store_true')
> +        parser.add_argument('--libcamera_generate_header',          action='store_true')
> +        parser.add_argument('--libcamera_generate_serializer',      action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_cpp',       action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_h',         action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_worker',    action='store_true')
> +        parser.add_argument('--libcamera_output_path')
> +        args = parser.parse_args(unparsed_args)
> +
> +        if not args.libcamera_generate_core_header and \
> +           not args.libcamera_generate_core_serializer:
> +            ValidateNamespace(self.module.mojom_namespace)
> +            ValidateInterfaces(self.module.interfaces)
> +            self.module_name = ModuleClassName(self.module)
> +
> +        fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path))
> +
> +        gen_funcs = [
> +                [args.libcamera_generate_core_header,     self._GenerateCoreHeader],
> +                [args.libcamera_generate_core_serializer, self._GenerateCoreSerializer],
> +                [args.libcamera_generate_header,          self._GenerateDataHeader],
> +                [args.libcamera_generate_serializer,      self._GenerateSerializer],
> +                [args.libcamera_generate_proxy_cpp,       self._GenerateProxyCpp],
> +                [args.libcamera_generate_proxy_h,         self._GenerateProxyHeader],
> +                [args.libcamera_generate_proxy_worker,    self._GenerateProxyWorker],
> +        ]
> +
> +        for pair in gen_funcs:
> +            if pair[0]:
> +                self.Write(pair[1](), args.libcamera_output_path)
Laurent Pinchart Feb. 12, 2021, 2:01 a.m. UTC | #2
Hi Paul,

Another comment.

On Fri, Feb 12, 2021 at 02:25:00AM +0200, Laurent Pinchart wrote:
> On Thu, Feb 11, 2021 at 04:17:57PM +0900, Paul Elder wrote:
> > Add templates to mojo to generate code for the IPC mechanism. These
> > templates generate:
> > - module header
> > - module serializer
> > - IPA proxy cpp, header, and worker
> > 
> > Given an input data definition mojom file for a pipeline.
> > 
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Acked-by: Jacopo Mondi <jacopo@jmondi.org>
> > Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > 
> > ---
> > Changes in v7:
> > - cosmetic changes, add a few todos
> > - use the new sendSync/sendAsync IPCPipe API
> >   - save the sequence number in the proxy
> >   - construct the header before calling sendSync/sendAsync (from the
> >     proxy)
> > - replace genHeader and genSerdes with skipHeader and skipSerdes in
> >   core.mojom
> > 
> > Changes in v6:
> > - add templates for core_ipa_interface.h and core_ipa_serializer.h
> >   - for libcamera types defined in mojom
> > - rename everything to {{module_name}}_ipa_{interface,proxy,proxy_worker}.{c,h}
> > - remove #include <libcamera/ipa/{{module_name}}.h
> > - support customizable start()
> > - remove the need for per-pipeline ControlInfoMap
> > - add todo for avoiding intermediate vectors
> > - remove postfix underscore for generated struct fields
> > - support structs that are members of vectors/maps that aren't defined
> >   in mojom (in mojom)
> > - fix has_fd detection
> > - namespacing is now required in mojom, in the form of ^ipa\.[0-9A-Za-z_]+
> > - support consts in mojom
> > - make the pseudo-switch-case in the python generator nicer
> > 
> > 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
> > ---
> >  .../core_ipa_interface.h.tmpl                 |  40 ++
> >  .../core_ipa_serializer.h.tmpl                |  47 ++
> >  .../definition_functions.tmpl                 |  53 ++
> >  .../libcamera_templates/meson.build           |  14 +
> >  .../module_ipa_interface.h.tmpl               |  87 +++
> >  .../module_ipa_proxy.cpp.tmpl                 | 236 ++++++++
> >  .../module_ipa_proxy.h.tmpl                   | 128 +++++
> >  .../module_ipa_proxy_worker.cpp.tmpl          | 226 ++++++++
> >  .../module_ipa_serializer.h.tmpl              |  48 ++
> >  .../libcamera_templates/proxy_functions.tmpl  | 194 +++++++
> >  .../libcamera_templates/serializer.tmpl       | 313 +++++++++++
> >  .../generators/mojom_libcamera_generator.py   | 508 ++++++++++++++++++
> >  12 files changed, 1894 insertions(+)
> >  create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/definition_functions.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/meson.build
> >  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
> >  create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl
> >  create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py

[snip]

> > 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..2bc187f2
> > --- /dev/null
> > +++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
> > @@ -0,0 +1,128 @@
> > +{#-
> > + # SPDX-License-Identifier: LGPL-2.1-or-later
> > + # Copyright (C) 2020, Google Inc.
> > +-#}
> > +{%- import "proxy_functions.tmpl" as proxy_funcs -%}
> > +
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * {{module_name}}_ipa_proxy.h - Image Processing Algorithm proxy for {{module_name}}
> > + *
> > + * This file is auto-generated. Do not edit.
> > + */
> > +
> > +#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
> > +#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
> > +
> > +#include <libcamera/ipa/ipa_interface.h>
> > +#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
> > +
> > +#include "libcamera/internal/control_serializer.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/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(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<uint8_t>::const_iterator data,
> > +		size_t dataSize,
> > +		const std::vector<int32_t> &fds);
> > +{% endfor %}
> > +
> > +	/* Helper class to invoke async functions in another thread. */
> > +	class ThreadProxy : public Object
> > +	{
> > +	public:
> > +		void setIPA({{interface_name}} *ipa)
> > +		{
> > +			ipa_ = ipa;
> > +		}
> > +
> > +		void stop()
> > +		{
> > +			ipa_->stop();
> > +		}
> > +{% for method in interface_main.methods %}
> > +{%- if method|is_async %}
> > +		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
> > +		{
> > +			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
> > +		}
> > +{%- elif method.mojom_name == "start" %}
> > +		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
> > +		{
> > +{%- if method|method_return_value != "void" %}
> > +			return ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
> > +{%- else %}
> > +			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}
> > +	{{- ", " if method|method_param_outputs|params_comma_sep -}}
> > +	{{- method|method_param_outputs|params_comma_sep}});
> > +{%- endif %}
> > +		}
> > +{%- endif %}
> > +{%- endfor %}
> > +
> > +	private:
> > +		{{interface_name}} *ipa_;
> > +	};
> > +
> > +	bool running_;
> > +	Thread thread_;
> > +	ThreadProxy proxy_;
> > +	std::unique_ptr<{{interface_name}}> ipa_;
> > +
> > +	const bool isolate_;
> > +
> > +	std::unique_ptr<IPCPipeUnixSocket> ipc_;
> > +
> > +	ControlSerializer controlSerializer_;
> > +
> > +	uint32_t seq_;

I don't think this belongs here, the sequence number should be an
internal property of the IPCPipe implementation. As it won't be
straightforward to solve, you can just add a todo comment here.

> > +};
> > +
> > +{%- if has_namespace %}
> > +{% for ns in namespace|reverse %}
> > +} /* namespace {{ns}} */
> > +{% endfor %}
> > +{%- endif %}
> > +} /* namespace libcamera */
> > +

[snip]

Patch
diff mbox series

diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
new file mode 100644
index 00000000..b253881b
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
@@ -0,0 +1,40 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "definition_functions.tmpl" as funcs -%}
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * core_ipa_interface.h - libcamera core definitions for Image Processing Algorithms
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#ifndef __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__
+#define __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__
+
+{% if has_map %}#include <map>{% endif %}
+{% if has_array %}#include <vector>{% endif %}
+
+#include <libcamera/ipa/ipa_interface.h>
+
+namespace libcamera {
+
+{# \todo Use constexpr instead of const after C++20 for std::string #}
+{% for const in consts %}
+static const {{const.kind|name}} {{const.mojom_name}} = {{const.value}};
+{% endfor %}
+
+{% for enum in enums %}
+{{funcs.define_enum(enum)}}
+{% endfor %}
+
+{%- for struct in structs_gen_header %}
+{{funcs.define_struct(struct)}}
+{% endfor %}
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPA_INTERFACE_CORE_GENERATED_H__ */
diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
new file mode 100644
index 00000000..37a784f1
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
@@ -0,0 +1,47 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "serializer.tmpl" as serializer -%}
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * core_ipa_serializer.h - Data serializer for core libcamera definitions for IPA
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__
+#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__
+
+#include <tuple>
+#include <vector>
+
+#include <libcamera/ipa/core_ipa_interface.h>
+
+#include "libcamera/internal/control_serializer.h"
+#include "libcamera/internal/ipa_data_serializer.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPADataSerializer)
+{% for struct in structs_gen_serializer %}
+template<>
+class IPADataSerializer<{{struct|name}}>
+{
+public:
+{{- serializer.serializer(struct, "")}}
+{%- if struct|has_fd %}
+{{serializer.deserializer_fd(struct, "")}}
+{%- else %}
+{{serializer.deserializer_no_fd(struct, "")}}
+{{serializer.deserializer_fd_simple(struct, "")}}
+{%- endif %}
+};
+{% endfor %}
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_CORE_H__ */
diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
new file mode 100644
index 00000000..cdd75f89
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
@@ -0,0 +1,53 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+
+{#
+ # \brief Generate enum definition
+ #
+ # \param enum Enum object whose definition is to be generated
+ #}
+{%- macro define_enum(enum) -%}
+enum {{enum.mojom_name}} {
+{%- for field in enum.fields %}
+	{{field.mojom_name}} = {{field.numeric_value}},
+{%- endfor %}
+};
+{%- endmacro -%}
+
+{#
+ # \brief Generate struct definition
+ #
+ # \param struct Struct object whose definition is to be generated
+ #}
+{%- macro define_struct(struct) -%}
+struct {{struct.mojom_name}}
+{
+public:
+	{{struct.mojom_name}}() {%- if struct|has_default_fields %}
+		:{% endif %}
+{%- for field in struct.fields|with_default_values -%}
+{{" " if loop.first}}{{field.mojom_name}}({{field|default_value}}){{", " if not loop.last}}
+{%- endfor %}
+	{
+	}
+
+	{{struct.mojom_name}}(
+{%- for field in struct.fields -%}
+{{"const " if not field|is_pod}}{{field|name}} {{"&" if not field|is_pod}}_{{field.mojom_name}}{{", " if not loop.last}}
+{%- endfor -%}
+)
+		:
+{%- for field in struct.fields -%}
+{{" " if loop.first}}{{field.mojom_name}}(_{{field.mojom_name}}){{", " if not loop.last}}
+{%- endfor %}
+	{
+	}
+{% for field in struct.fields %}
+	{{field|name}} {{field.mojom_name}};
+{%- endfor %}
+};
+{%- endmacro -%}
+
+
diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/ipc/generators/libcamera_templates/meson.build
new file mode 100644
index 00000000..70664eab
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/meson.build
@@ -0,0 +1,14 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+mojom_template_files = files([
+    'core_ipa_interface.h.tmpl',
+    'core_ipa_serializer.h.tmpl',
+    'definition_functions.tmpl',
+    'module_ipa_interface.h.tmpl',
+    'module_ipa_proxy.cpp.tmpl',
+    'module_ipa_proxy.h.tmpl',
+    'module_ipa_proxy_worker.cpp.tmpl',
+    'module_ipa_serializer.h.tmpl',
+    'proxy_functions.tmpl',
+    'serializer.tmpl',
+])
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
new file mode 100644
index 00000000..ebe811fa
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
@@ -0,0 +1,87 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "definition_functions.tmpl" as funcs -%}
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}}
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#ifndef __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__
+#define __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__
+
+#include <libcamera/ipa/core_ipa_interface.h>
+#include <libcamera/ipa/ipa_interface.h>
+
+{% if has_map %}#include <map>{% endif %}
+{% if has_array %}#include <vector>{% endif %}
+
+namespace libcamera {
+{%- if has_namespace %}
+{% for ns in namespace %}
+namespace {{ns}} {
+{% endfor %}
+{%- endif %}
+
+{% for const in consts %}
+const {{const.kind|name}} {{const.mojom_name}} = {{const.value}};
+{% endfor %}
+
+enum class {{cmd_enum_name}} {
+	Exit = 0,
+{%- for method in interface_main.methods %}
+	{{method.mojom_name|cap}} = {{loop.index}},
+{%- endfor %}
+};
+
+enum class {{cmd_event_enum_name}} {
+{%- for method in interface_event.methods %}
+	{{method.mojom_name|cap}} = {{loop.index}},
+{%- endfor %}
+};
+
+{% for enum in enums %}
+{{funcs.define_enum(enum)}}
+{% endfor %}
+
+{%- for struct in structs_nonempty %}
+{{funcs.define_struct(struct)}}
+{% endfor %}
+
+{#-
+Any consts or #defines should be moved to the mojom file.
+#}
+class {{interface_name}} : public IPAInterface
+{
+public:
+{% for method in interface_main.methods %}
+	virtual {{method|method_return_value}} {{method.mojom_name}}(
+{%- for param in method|method_parameters %}
+		{{param}}{{- "," if not loop.last}}
+{%- endfor -%}
+) = 0;
+{% endfor %}
+
+{%- for method in interface_event.methods %}
+	Signal<
+{%- for param in method.parameters -%}
+		{{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod}}
+		{{- ", " if not loop.last}}
+{%- endfor -%}
+> {{method.mojom_name}};
+{% endfor -%}
+};
+
+{%- if has_namespace %}
+{% for ns in namespace|reverse %}
+} /* namespace {{ns}} */
+{% endfor %}
+{%- endif %}
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_IPA_INTERFACE_{{module_name|upper}}_GENERATED_H__ */
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
new file mode 100644
index 00000000..ba34a361
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
@@ -0,0 +1,236 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "proxy_functions.tmpl" as proxy_funcs -%}
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * {{module_name}}_ipa_proxy.cpp - Image Processing Algorithm proxy for {{module_name}}
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#include <libcamera/ipa/{{module_name}}_ipa_proxy.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <libcamera/ipa/ipa_module_info.h>
+#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
+#include <libcamera/ipa/{{module_name}}_ipa_serializer.h>
+
+#include "libcamera/internal/control_serializer.h"
+#include "libcamera/internal/ipa_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), seq_(0)
+{
+	LOG(IPAProxy, Debug)
+		<< "initializing {{module_name}} proxy: loading IPA from "
+		<< ipam->path();
+
+	if (isolate_) {
+		const std::string proxyWorkerPath = resolvePath("{{module_name}}_ipa_proxy");
+		if (proxyWorkerPath.empty()) {
+			LOG(IPAProxy, Error)
+				<< "Failed to get proxy worker path";
+			return;
+		}
+
+		ipc_ = std::make_unique<IPCPipeUnixSocket>(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_) {
+		IPCMessage::Header header =
+			{ static_cast<uint32_t>({{cmd_enum_name}}::Exit), seq_++ };
+		IPCMessage msg(header);
+		ipc_->sendAsync(msg);
+	}
+}
+
+{% if interface_event.methods|length > 0 %}
+void {{proxy_name}}::recvMessage(const IPCMessage &data)
+{
+	size_t dataSize = data.data().size();
+	{{cmd_event_enum_name}} _cmd = static_cast<{{cmd_event_enum_name}}>(data.header().cmd);
+
+	switch (_cmd) {
+{%- for method in interface_event.methods %}
+	case {{cmd_event_enum_name}}::{{method.mojom_name|cap}}: {
+		{{method.mojom_name}}IPC(data.data().cbegin(), dataSize, data.fds());
+		break;
+	}
+{%- endfor %}
+	default:
+		LOG(IPAProxy, Error) << "Unknown command " << static_cast<uint32_t>(_cmd);
+	}
+}
+{%- endif %}
+
+{% for method in interface_main.methods %}
+{{proxy_funcs.func_sig(proxy_name, method)}}
+{
+	if (isolate_)
+		{{"return " if method|method_return_value != "void"}}{{method.mojom_name}}IPC(
+{%- for param in method|method_param_names -%}
+		{{param}}{{- ", " if not loop.last}}
+{%- endfor -%}
+);
+	else
+		{{"return " if method|method_return_value != "void"}}{{method.mojom_name}}Thread(
+{%- for param in method|method_param_names -%}
+		{{param}}{{- ", " if not loop.last}}
+{%- endfor -%}
+);
+}
+
+{{proxy_funcs.func_sig(proxy_name, method, "Thread")}}
+{
+{%- if method.mojom_name == "init" %}
+	{{proxy_funcs.init_thread_body()}}
+{%- elif method.mojom_name == "stop" %}
+	{{proxy_funcs.stop_thread_body()}}
+{%- elif method.mojom_name == "start" %}
+	running_ = true;
+	thread_.start();
+
+	{{ "return " if method|method_return_value != "void" -}}
+	proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking
+	{{- ", " if method|method_param_names}}
+	{%- for param in method|method_param_names -%}
+		{{param}}{{- ", " if not loop.last}}
+	{%- endfor -%}
+);
+{%- elif not method|is_async %}
+	{{ "return " if method|method_return_value != "void" -}}
+	ipa_->{{method.mojom_name}}(
+	{%- for param in method|method_param_names -%}
+		{{param}}{{- ", " if not loop.last}}
+	{%- endfor -%}
+);
+{% elif method|is_async %}
+	proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued,
+	{%- for param in method|method_param_names -%}
+		{{param}}{{- ", " if not loop.last}}
+	{%- endfor -%}
+);
+{%- endif %}
+}
+
+{{proxy_funcs.func_sig(proxy_name, method, "IPC")}}
+{
+{%- set has_input = true if method|method_param_inputs|length > 0 %}
+{%- set has_output = true if method|method_param_outputs|length > 0 or method|method_return_value != "void" %}
+{%- set cmd = cmd_enum_name + "::" + method.mojom_name|cap %}
+	IPCMessage::Header _header = { static_cast<uint32_t>({{cmd}}), seq_++ };
+	IPCMessage _ipcInputBuf(_header);
+{%- if has_output %}
+	IPCMessage _ipcOutputBuf;
+{%- endif %}
+
+{{proxy_funcs.serialize_call(method|method_param_inputs, '_ipcInputBuf.data()', '_ipcInputBuf.fds()')}}
+
+{% if method|is_async %}
+	int _ret = ipc_->sendAsync(_ipcInputBuf);
+{%- else %}
+	int _ret = ipc_->sendSync(_ipcInputBuf
+{{- ", &_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<uint8_t>::const_iterator data,
+	size_t dataSize,
+	[[maybe_unused]] const std::vector<int32_t> &fds)
+{
+{%- for param in method.parameters %}
+	{{param|name}} {{param.mojom_name}};
+{%- endfor %}
+{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}}
+	{{method.mojom_name}}.emit({{method.parameters|params_comma_sep}});
+}
+{% endfor %}
+
+{%- if has_namespace %}
+{% for ns in namespace|reverse %}
+} /* 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..2bc187f2
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
@@ -0,0 +1,128 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "proxy_functions.tmpl" as proxy_funcs -%}
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * {{module_name}}_ipa_proxy.h - Image Processing Algorithm proxy for {{module_name}}
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#ifndef __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
+#define __LIBCAMERA_INTERNAL_IPA_PROXY_{{module_name|upper}}_H__
+
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
+
+#include "libcamera/internal/control_serializer.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/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(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<uint8_t>::const_iterator data,
+		size_t dataSize,
+		const std::vector<int32_t> &fds);
+{% endfor %}
+
+	/* Helper class to invoke async functions in another thread. */
+	class ThreadProxy : public Object
+	{
+	public:
+		void setIPA({{interface_name}} *ipa)
+		{
+			ipa_ = ipa;
+		}
+
+		void stop()
+		{
+			ipa_->stop();
+		}
+{% for method in interface_main.methods %}
+{%- if method|is_async %}
+		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
+		{
+			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
+		}
+{%- elif method.mojom_name == "start" %}
+		{{proxy_funcs.func_sig(proxy_name, method, "", false)|indent(16)}}
+		{
+{%- if method|method_return_value != "void" %}
+			return ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}});
+{%- else %}
+			ipa_->{{method.mojom_name}}({{method.parameters|params_comma_sep}}
+	{{- ", " if method|method_param_outputs|params_comma_sep -}}
+	{{- method|method_param_outputs|params_comma_sep}});
+{%- endif %}
+		}
+{%- endif %}
+{%- endfor %}
+
+	private:
+		{{interface_name}} *ipa_;
+	};
+
+	bool running_;
+	Thread thread_;
+	ThreadProxy proxy_;
+	std::unique_ptr<{{interface_name}}> ipa_;
+
+	const bool isolate_;
+
+	std::unique_ptr<IPCPipeUnixSocket> ipc_;
+
+	ControlSerializer controlSerializer_;
+
+	uint32_t seq_;
+};
+
+{%- 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..ac037fa1
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
@@ -0,0 +1,226 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "proxy_functions.tmpl" as proxy_funcs -%}
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * {{module_name}}_ipa_proxy_worker.cpp - Image Processing Algorithm proxy worker for {{module_name}}
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+{#- \todo Split proxy worker into IPC worker and proxy worker. #}
+
+#include <algorithm>
+#include <iostream>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
+#include <libcamera/ipa/{{module_name}}_ipa_serializer.h>
+#include <libcamera/logging.h>
+
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/control_serializer.h"
+#include "libcamera/internal/event_dispatcher.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/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<uint8_t> _callRetBuf;
+			std::tie(_callRetBuf, std::ignore) =
+				IPADataSerializer<{{method|method_return_value}}>::serialize(_callRet);
+			_response.data().insert(_response.data().end(), _callRetBuf.cbegin(), _callRetBuf.cend());
+{%- else %}
+		{{proxy_funcs.serialize_call(method|method_param_outputs, "_response.data()", "_response.fds()")|indent(16, true)}}
+{%- endif %}
+			int _ret = socket_.send(_response.payload());
+			if (_ret < 0) {
+				LOG({{proxy_worker_name}}, Error)
+					<< "Reply to {{method.mojom_name}}() failed: " << _ret;
+			}
+			LOG({{proxy_worker_name}}, Debug) << "Done replying to {{method.mojom_name}}()";
+{%- endif %}
+			break;
+		}
+{% endfor %}
+		default:
+			LOG({{proxy_worker_name}}, Error) << "Unknown command " << _ipcMessage.header().cmd;
+		}
+	}
+
+	int init(std::unique_ptr<IPAModule> &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<uint32_t>({{cmd_event_enum_name}}::{{method.mojom_name|cap}}),
+			0
+		};
+		IPCMessage _message(header);
+
+		{{proxy_funcs.serialize_call(method|method_param_inputs, "_message.data()", "_message.fds()")}}
+
+		socket_.send(_message.payload());
+
+		LOG({{proxy_worker_name}}, Debug) << "{{method.mojom_name}} done";
+	}
+{% endfor %}
+
+	{{interface_name}} *ipa_;
+	IPCUnixSocket socket_;
+
+	ControlSerializer controlSerializer_;
+
+	bool exit_;
+};
+
+int main(int argc, char **argv)
+{
+{#- \todo Handle enabling debugging more dynamically. #}
+	/* Uncomment this for debugging. */
+#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 <path to IPA so> <fd to bind unix socket>";
+		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<IPAModule> ipam = std::make_unique<IPAModule>(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 initialized";
+
+	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..64ae99dc
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
@@ -0,0 +1,48 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{%- import "serializer.tmpl" as serializer -%}
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * {{module_name}}_ipa_serializer.h - Image Processing Algorithm data serializer for {{module_name}}
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__
+#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_{{module_name|upper}}_H__
+
+#include <tuple>
+#include <vector>
+
+#include <libcamera/ipa/{{module_name}}_ipa_interface.h>
+#include <libcamera/ipa/core_ipa_serializer.h>
+
+#include "libcamera/internal/control_serializer.h"
+#include "libcamera/internal/ipa_data_serializer.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPADataSerializer)
+{% for struct in structs_nonempty %}
+template<>
+class IPADataSerializer<{{struct|name_full(namespace_str)}}>
+{
+public:
+{{- serializer.serializer(struct, namespace_str)}}
+{%- if struct|has_fd %}
+{{serializer.deserializer_fd(struct, namespace_str)}}
+{%- else %}
+{{serializer.deserializer_no_fd(struct, namespace_str)}}
+{{serializer.deserializer_fd_simple(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..40611feb
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
@@ -0,0 +1,194 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{#
+ # \brief Generate function prototype
+ #
+ # \param class Class name
+ # \param method mojom Method object
+ # \param suffix Suffix to append to \a method function name
+ # \param need_class_name If true, generate class name with function
+ # \param override If true, generate override tag after the function prototype
+ #}
+{%- macro func_sig(class, method, suffix = "", need_class_name = true, override = false) -%}
+{{method|method_return_value}} {{class + "::" if need_class_name}}{{method.mojom_name}}{{suffix}}(
+{%- for param in method|method_parameters %}
+	{{param}}{{- "," if not loop.last}}
+{%- endfor -%}
+){{" override" if override}}
+{%- endmacro -%}
+
+{#
+ # \brief Generate function body for IPA init() function for thread
+ #}
+{%- macro init_thread_body() -%}
+	int ret = ipa_->init(settings);
+	if (ret)
+		return ret;
+
+	proxy_.moveToThread(&thread_);
+
+	return 0;
+{%- endmacro -%}
+
+{#
+ # \brief Generate function body for IPA stop() function for thread
+ #}
+{%- macro stop_thread_body() -%}
+	if (!running_)
+		return;
+
+	running_ = false;
+
+	proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking);
+
+	thread_.exit();
+	thread_.wait();
+{%- endmacro -%}
+
+
+{#
+ # \brief Serialize multiple objects into data buffer and fd vector
+ #
+ # Generate code to serialize multiple objects, as specified in \a params
+ # (which are the parameters to some function), into \a buf data buffer and
+ # \a fds fd vector.
+ # This code is meant to be used by the proxy, for serializing prior to IPC calls.
+ #
+ # \todo Avoid intermediate vectors
+ #}
+{%- macro serialize_call(params, buf, fds) %}
+{%- for param in params %}
+	std::vector<uint8_t> {{param.mojom_name}}Buf;
+{%- if param|has_fd %}
+	std::vector<int32_t> {{param.mojom_name}}Fds;
+	std::tie({{param.mojom_name}}Buf, {{param.mojom_name}}Fds) =
+{%- else %}
+	std::tie({{param.mojom_name}}Buf, std::ignore) =
+{%- endif %}
+		IPADataSerializer<{{param|name}}>::serialize({{param.mojom_name}}
+{{- ", &controlSerializer_" if param|needs_control_serializer -}}
+);
+{%- endfor %}
+
+{%- if params|length > 1 %}
+{%- for param in params %}
+	appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Buf.size());
+{%- if param|has_fd %}
+	appendPOD<uint32_t>({{buf}}, {{param.mojom_name}}Fds.size());
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+
+{%- for param in params %}
+	{{buf}}.insert({{buf}}.end(), {{param.mojom_name}}Buf.begin(), {{param.mojom_name}}Buf.end());
+{%- endfor %}
+
+{%- for param in params %}
+{%- if param|has_fd %}
+	{{fds}}.insert({{fds}}.end(), {{param.mojom_name}}Fds.begin(), {{param.mojom_name}}Fds.end());
+{%- endif %}
+{%- endfor %}
+{%- endmacro -%}
+
+
+{#
+ # \brief Deserialize a single object from data buffer and fd vector
+ #
+ # \param pointer If true, deserialize the object into a dereferenced pointer
+ # \param iter If true, treat \a buf as an iterator instead of a vector
+ # \param data_size Variable that holds the size of the vector referenced by \a buf
+ #
+ # Generate code to deserialize a single object, as specified in \a param,
+ # from \a buf data buffer and \a fds fd vector.
+ # This code is meant to be used by macro deserialize_call.
+ #}
+{%- macro deserialize_param(param, pointer, loop, buf, fds, iter, data_size) -%}
+{{"*" if pointer}}{{param.mojom_name}} = IPADataSerializer<{{param|name}}>::deserialize(
+	{{buf}}{{- ".cbegin()" if not iter}} + {{param.mojom_name}}Start,
+{%- if loop.last and not iter %}
+	{{buf}}.cend()
+{%- elif not iter %}
+	{{buf}}.cbegin() + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize
+{%- elif iter and loop.length == 1 %}
+	{{buf}} + {{data_size}}
+{%- else %}
+	{{buf}} + {{param.mojom_name}}Start + {{param.mojom_name}}BufSize
+{%- endif -%}
+{{- "," if param|has_fd}}
+{%- if param|has_fd %}
+	{{fds}}.cbegin() + {{param.mojom_name}}FdStart,
+{%- if loop.last %}
+	{{fds}}.cend()
+{%- else %}
+	{{fds}}.cbegin() + {{param.mojom_name}}FdStart + {{param.mojom_name}}FdsSize
+{%- endif -%}
+{%- endif -%}
+{{- "," if param|needs_control_serializer}}
+{%- if param|needs_control_serializer %}
+	&controlSerializer_
+{%- endif -%}
+);
+{%- endmacro -%}
+
+
+{#
+ # \brief Deserialize multiple objects from data buffer and fd vector
+ #
+ # \param pointer If true, deserialize objects into pointers, and adds a null check.
+ # \param declare If true, declare the objects in addition to deserialization.
+ # \param iter if true, treat \a buf as an iterator instead of a vector
+ # \param data_size Variable that holds the size of the vector referenced by \a buf
+ #
+ # Generate code to deserialize multiple objects, as specified in \a params
+ # (which are the parameters to some function), from \a buf data buffer and
+ # \a fds fd vector.
+ # This code is meant to be used by the proxy, for deserializing after IPC calls.
+ #
+ # \todo Avoid intermediate vectors
+ #}
+{%- macro deserialize_call(params, buf, fds, pointer = true, declare = false, iter = false, data_size = '') -%}
+{% set ns = namespace(size_offset = 0) %}
+{%- if params|length > 1 %}
+{%- for param in params %}
+	[[maybe_unused]] const size_t {{param.mojom_name}}BufSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}}
+{%- if iter -%}
+, {{buf}} + {{data_size}}
+{%- endif -%}
+);
+	{%- set ns.size_offset = ns.size_offset + 4 %}
+{%- if param|has_fd %}
+	[[maybe_unused]] const size_t {{param.mojom_name}}FdsSize = readPOD<uint32_t>({{buf}}, {{ns.size_offset}}
+{%- if iter -%}
+, {{buf}} + {{data_size}}
+{%- endif -%}
+);
+	{%- set ns.size_offset = ns.size_offset + 4 %}
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+{% for param in params %}
+{%- if loop.first %}
+	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..af35b9e3
--- /dev/null
+++ b/utils/ipc/generators/libcamera_templates/serializer.tmpl
@@ -0,0 +1,313 @@ 
+{#-
+ # SPDX-License-Identifier: LGPL-2.1-or-later
+ # Copyright (C) 2020, Google Inc.
+-#}
+{#
+ # \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 a field into return vector
+ #
+ # Generate code to serialize \a field into retData, including size of the
+ # field and fds (where appropriate).
+ # This code is meant to be used by the IPADataSerializer specialization.
+ #
+ # \todo Avoid intermediate vectors
+ #}
+{%- macro serializer_field(field, namespace, loop) %}
+{%- if field|is_pod or field|is_enum %}
+		std::vector<uint8_t> {{field.mojom_name}};
+		std::tie({{field.mojom_name}}, std::ignore) =
+	{%- if field|is_pod %}
+			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
+	{%- elif field|is_enum %}
+			IPADataSerializer<uint{{field|bit_width}}_t>::serialize(data.{{field.mojom_name}});
+	{%- endif %}
+		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
+{%- elif field|is_fd %}
+		std::vector<uint8_t> {{field.mojom_name}};
+		std::vector<int32_t> {{field.mojom_name}}Fds;
+		std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =
+			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
+		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
+		retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());
+{%- elif field|is_controls %}
+		if (data.{{field.mojom_name}}.size() > 0) {
+			std::vector<uint8_t> {{field.mojom_name}};
+			std::tie({{field.mojom_name}}, std::ignore) =
+				IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}, cs);
+			appendPOD<uint32_t>(retData, {{field.mojom_name}}.size());
+			retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
+		} else {
+			appendPOD<uint32_t>(retData, 0);
+		}
+{%- elif field|is_plain_struct or field|is_array or field|is_map or field|is_str %}
+		std::vector<uint8_t> {{field.mojom_name}};
+	{%- if field|has_fd %}
+		std::vector<int32_t> {{field.mojom_name}}Fds;
+		std::tie({{field.mojom_name}}, {{field.mojom_name}}Fds) =
+	{%- else %}
+		std::tie({{field.mojom_name}}, std::ignore) =
+	{%- endif %}
+	{%- if field|is_array or field|is_map %}
+			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}}, cs);
+	{%- elif field|is_str %}
+			IPADataSerializer<{{field|name}}>::serialize(data.{{field.mojom_name}});
+	{%- else %}
+			IPADataSerializer<{{field|name_full(namespace)}}>::serialize(data.{{field.mojom_name}}, cs);
+	{%- endif %}
+		appendPOD<uint32_t>(retData, {{field.mojom_name}}.size());
+	{%- if field|has_fd %}
+		appendPOD<uint32_t>(retData, {{field.mojom_name}}Fds.size());
+	{%- endif %}
+		retData.insert(retData.end(), {{field.mojom_name}}.begin(), {{field.mojom_name}}.end());
+	{%- if field|has_fd %}
+		retFds.insert(retFds.end(), {{field.mojom_name}}Fds.begin(), {{field.mojom_name}}Fds.end());
+	{%- endif %}
+{%- else %}
+		/* Unknown serialization for {{field.mojom_name}}. */
+{%- endif %}
+{%- endmacro %}
+
+
+{#
+ # \brief Deserialize a field into return struct
+ #
+ # Generate code to deserialize \a field into object ret.
+ # This code is meant to be used by the IPADataSerializer specialization.
+ #}
+{%- macro deserializer_field(field, namespace, loop) %}
+{% if field|is_pod or field|is_enum %}
+	{%- set field_size = (field|bit_width|int / 8)|int %}
+		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
+		{%- if field|is_pod %}
+		ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field_size}});
+		{%- else %}
+		ret.{{field.mojom_name}} = static_cast<{{field|name_full(namespace)}}>(IPADataSerializer<uint{{field|bit_width}}_t>::deserialize(m, m + {{field_size}}));
+		{%- endif %}
+	{%- if not loop.last %}
+		m += {{field_size}};
+		dataSize -= {{field_size}};
+	{%- endif %}
+{% elif field|is_fd %}
+	{%- set field_size = 1 %}
+		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
+		ret.{{field.mojom_name}} = IPADataSerializer<{{field|name}}>::deserialize(m, m + 1, n, n + 1, cs);
+	{%- if not loop.last %}
+		m += {{field_size}};
+		dataSize -= {{field_size}};
+		n += ret.{{field.mojom_name}}.isValid() ? 1 : 0;
+		fdsSize -= ret.{{field.mojom_name}}.isValid() ? 1 : 0;
+	{%- endif %}
+{% elif field|is_controls %}
+	{%- set field_size = 4 %}
+		{{- check_data_size(field_size, 'dataSize', field.mojom_name + 'Size', 'data')}}
+		const size_t {{field.mojom_name}}Size = readPOD<uint32_t>(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<uint32_t>(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<uint32_t>(m, 0, dataEnd);
+		m += {{field_size}};
+		dataSize -= {{field_size}};
+		{{- check_data_size(field.mojom_name + 'FdsSize', 'fdsSize', field.mojom_name, 'fds')}}
+	{%- endif %}
+	{%- set field_size = field.mojom_name + 'Size' -%}
+		{{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}}
+		ret.{{field.mojom_name}} =
+	{%- if field|is_str %}
+			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size);
+	{%- elif field|has_fd and (field|is_array or field|is_map) %}
+			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs);
+	{%- elif field|has_fd and (not (field|is_array or field|is_map)) %}
+			IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m, m + {{field.mojom_name}}Size, n, n + {{field.mojom_name}}FdsSize, cs);
+	{%- elif (not field|has_fd) and (field|is_array or field|is_map) %}
+			IPADataSerializer<{{field|name}}>::deserialize(m, m + {{field.mojom_name}}Size, cs);
+	{%- else %}
+			IPADataSerializer<{{field|name_full(namespace)}}>::deserialize(m, m + {{field.mojom_name}}Size, cs);
+	{%- endif %}
+	{%- if not loop.last %}
+		m += {{field_size}};
+		dataSize -= {{field_size}};
+	{%- if field|has_fd %}
+		n += {{field.mojom_name}}FdsSize;
+		fdsSize -= {{field.mojom_name}}FdsSize;
+	{%- endif %}
+	{%- endif %}
+{% else %}
+		/* Unknown deserialization for {{field.mojom_name}}. */
+{%- endif %}
+{%- endmacro %}
+
+
+{#
+ # \brief Serialize a struct
+ #
+ # Generate code for IPADataSerializer specialization, for serializing
+ # \a struct.
+ #}
+{%- macro serializer(struct, namespace) %}
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const {{struct|name_full(namespace)}} &data,
+{%- if struct|needs_control_serializer %}
+		  ControlSerializer *cs)
+{%- else %}
+		  [[maybe_unused]] ControlSerializer *cs = nullptr)
+{%- endif %}
+	{
+		std::vector<uint8_t> retData;
+{%- if struct|has_fd %}
+		std::vector<int32_t> retFds;
+{%- endif %}
+{%- for field in struct.fields %}
+{{serializer_field(field, namespace, loop)}}
+{%- endfor %}
+{% if struct|has_fd %}
+		return {retData, retFds};
+{%- else %}
+		return {retData, {}};
+{%- endif %}
+	}
+{%- endmacro %}
+
+
+{#
+ # \brief Deserialize a struct that has fds
+ #
+ # Generate code for IPADataSerializer specialization, for deserializing
+ # \a struct, in the case that \a struct has file descriptors.
+ #}
+{%- macro deserializer_fd(struct, namespace) %}
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t> &data,
+		    std::vector<int32_t> &fds,
+{%- if struct|needs_control_serializer %}
+		    ControlSerializer *cs)
+{%- else %}
+		    ControlSerializer *cs = nullptr)
+{%- endif %}
+	{
+		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), fds.cbegin(), fds.cend(), cs);
+	}
+
+{# \todo Don't inline this function #}
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+		    std::vector<uint8_t>::const_iterator dataEnd,
+		    std::vector<int32_t>::const_iterator fdsBegin,
+		    std::vector<int32_t>::const_iterator fdsEnd,
+{%- if struct|needs_control_serializer %}
+		    ControlSerializer *cs)
+{%- else %}
+		    [[maybe_unused]] ControlSerializer *cs = nullptr)
+{%- endif %}
+	{
+		{{struct|name_full(namespace)}} ret;
+		std::vector<uint8_t>::const_iterator m = dataBegin;
+		std::vector<int32_t>::const_iterator n = fdsBegin;
+
+		size_t dataSize = std::distance(dataBegin, dataEnd);
+		[[maybe_unused]] size_t fdsSize = std::distance(fdsBegin, fdsEnd);
+{%- for field in struct.fields -%}
+{{deserializer_field(field, namespace, loop)}}
+{%- endfor %}
+		return ret;
+	}
+{%- endmacro %}
+
+{#
+ # \brief Deserialize a struct that has fds, using non-fd
+ #
+ # Generate code for IPADataSerializer specialization, for deserializing
+ # \a struct, in the case that \a struct has no file descriptors but requires
+ # deserializers with file descriptors.
+ #}
+{%- macro deserializer_fd_simple(struct, namespace) %}
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t> &data,
+		    [[maybe_unused]] std::vector<int32_t> &fds,
+		    ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs);
+	}
+
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+		    std::vector<uint8_t>::const_iterator dataEnd,
+		    [[maybe_unused]] std::vector<int32_t>::const_iterator fdsBegin,
+		    [[maybe_unused]] std::vector<int32_t>::const_iterator fdsEnd,
+		    ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(dataBegin, dataEnd, cs);
+	}
+{%- endmacro %}
+
+
+{#
+ # \brief Deserialize a struct that has no fds
+ #
+ # Generate code for IPADataSerializer specialization, for deserializing
+ # \a struct, in the case that \a struct does not have file descriptors.
+ #}
+{%- macro deserializer_no_fd(struct, namespace) %}
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t> &data,
+{%- if struct|needs_control_serializer %}
+		    ControlSerializer *cs)
+{%- else %}
+		    ControlSerializer *cs = nullptr)
+{%- endif %}
+	{
+		return IPADataSerializer<{{struct|name_full(namespace)}}>::deserialize(data.cbegin(), data.cend(), cs);
+	}
+
+{# \todo Don't inline this function #}
+	static {{struct|name_full(namespace)}}
+	deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+		    std::vector<uint8_t>::const_iterator dataEnd,
+{%- if struct|needs_control_serializer %}
+		    ControlSerializer *cs)
+{%- else %}
+		    [[maybe_unused]] ControlSerializer *cs = nullptr)
+{%- endif %}
+	{
+		{{struct|name_full(namespace)}} ret;
+		std::vector<uint8_t>::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..438e41c6
--- /dev/null
+++ b/utils/ipc/generators/mojom_libcamera_generator.py
@@ -0,0 +1,508 @@ 
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2020, Google Inc.
+#
+# Author: Paul Elder <paul.elder@ideasonboard.com>
+#
+# mojom_libcamera_generator.py - Generates libcamera files from a mojom.Module.
+
+import argparse
+import datetime
+import os
+import re
+
+import mojom.fileutil as fileutil
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+from mojom.generate.template_expander import UseJinja
+
+
+GENERATOR_PREFIX = 'libcamera'
+
+_kind_to_cpp_type = {
+    mojom.BOOL:   'bool',
+    mojom.INT8:   'int8_t',
+    mojom.UINT8:  'uint8_t',
+    mojom.INT16:  'int16_t',
+    mojom.UINT16: 'uint16_t',
+    mojom.INT32:  'int32_t',
+    mojom.UINT32: 'uint32_t',
+    mojom.FLOAT:  'float',
+    mojom.INT64:  'int64_t',
+    mojom.UINT64: 'uint64_t',
+    mojom.DOUBLE: 'double',
+}
+
+_bit_widths = {
+    mojom.BOOL:   '8',
+    mojom.INT8:   '8',
+    mojom.UINT8:  '8',
+    mojom.INT16:  '16',
+    mojom.UINT16: '16',
+    mojom.INT32:  '32',
+    mojom.UINT32: '32',
+    mojom.FLOAT:  '32',
+    mojom.INT64:  '64',
+    mojom.UINT64: '64',
+    mojom.DOUBLE: '64',
+}
+
+def ModuleName(path):
+    return path.split('/')[-1].split('.')[0]
+
+def ModuleClassName(module):
+    return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1),
+                  module.interfaces[0].mojom_name)
+
+def Capitalize(name):
+    return name[0].upper() + name[1:]
+
+def ConstantStyle(name):
+    return generator.ToUpperSnakeCase(name)
+
+def Choose(cond, t, f):
+    return t if cond else f
+
+def CommaSep(l):
+    return ', '.join([m for m in l])
+
+def ParamsCommaSep(l):
+    return ', '.join([m.mojom_name for m in l])
+
+def GetDefaultValue(element):
+    if element.default is not None:
+        return element.default
+    if type(element.kind) == mojom.Kind:
+        return '0'
+    if mojom.IsEnumKind(element.kind):
+        return f'static_cast<{element.kind.mojom_name}>(0)'
+    if isinstance(element.kind, mojom.Struct) and \
+       element.kind.mojom_name == 'FileDescriptor':
+        return '-1'
+    return ''
+
+def HasDefaultValue(element):
+    return GetDefaultValue(element) != ''
+
+def HasDefaultFields(element):
+    return True in [HasDefaultValue(x) for x in element.fields]
+
+def GetAllTypes(element):
+    if mojom.IsArrayKind(element):
+        return GetAllTypes(element.kind)
+    if mojom.IsMapKind(element):
+        return GetAllTypes(element.key_kind) + GetAllTypes(element.value_kind)
+    if isinstance(element, mojom.Parameter):
+        return GetAllTypes(element.kind)
+    if mojom.IsEnumKind(element):
+        return [element.mojom_name]
+    if not mojom.IsStructKind(element):
+        return [element.spec]
+    if len(element.fields) == 0:
+        return [element.mojom_name]
+    ret = [GetAllTypes(x.kind) for x in element.fields]
+    ret = [x for sublist in ret for x in sublist]
+    return list(set(ret))
+
+def GetAllAttrs(element):
+    if mojom.IsArrayKind(element):
+        return GetAllAttrs(element.kind)
+    if mojom.IsMapKind(element):
+        return {**GetAllAttrs(element.key_kind), **GetAllAttrs(element.value_kind)}
+    if isinstance(element, mojom.Parameter):
+        return GetAllAttrs(element.kind)
+    if mojom.IsEnumKind(element):
+        return element.attributes if element.attributes is not None else {}
+    if mojom.IsStructKind(element) and len(element.fields) == 0:
+        return element.attributes if element.attributes is not None else {}
+    if not mojom.IsStructKind(element):
+        if hasattr(element, 'attributes'):
+            return element.attributes or {}
+        return {}
+    attrs = [(x.attributes) for x in element.fields]
+    ret = {}
+    for d in attrs:
+        ret.update(d or {})
+    if hasattr(element, 'attributes'):
+        ret.update(element.attributes or {})
+    return ret
+
+def NeedsControlSerializer(element):
+    types = GetAllTypes(element)
+    return "ControlList" in types or "ControlInfoMap" in types
+
+def HasFd(element):
+    attrs = GetAllAttrs(element)
+    if isinstance(element, mojom.Kind):
+        types = GetAllTypes(element)
+    else:
+        types = GetAllTypes(element.kind)
+    return "FileDescriptor" in types or (attrs is not None and "hasFd" in attrs)
+
+def WithDefaultValues(element):
+    return [x for x in element if HasDefaultValue(x)]
+
+def WithFds(element):
+    return [x for x in element if HasFd(x)]
+
+def MethodParamInputs(method):
+    return method.parameters
+
+def MethodParamOutputs(method):
+    if (MethodReturnValue(method) != 'void' or
+        method.response_parameters is None):
+        return []
+    return method.response_parameters
+
+def MethodParamsHaveFd(parameters):
+    return len([x for x in parameters if HasFd(x)]) > 0
+
+def MethodInputHasFd(method):
+    return MethodParamsHaveFd(method.parameters)
+
+def MethodOutputHasFd(method):
+    return MethodParamsHaveFd(MethodParamOutputs(method))
+
+def MethodParamNames(method):
+    params = []
+    for param in method.parameters:
+        params.append(param.mojom_name)
+    if MethodReturnValue(method) == 'void':
+        if method.response_parameters is None:
+            return params
+        for param in method.response_parameters:
+            params.append(param.mojom_name)
+    return params
+
+def MethodParameters(method):
+    params = []
+    for param in method.parameters:
+        params.append('const %s %s%s' % (GetNameForElement(param),
+                                         '&' if not IsPod(param) else '',
+                                         param.mojom_name))
+    if MethodReturnValue(method) == 'void':
+        if method.response_parameters is None:
+            return params
+        for param in method.response_parameters:
+            params.append(f'{GetNameForElement(param)} *{param.mojom_name}')
+    return params
+
+def MethodReturnValue(method):
+    if method.response_parameters is None:
+        return 'void'
+    if len(method.response_parameters) == 1 and IsPod(method.response_parameters[0]):
+        return GetNameForElement(method.response_parameters[0])
+    return 'void'
+
+def IsAsync(method):
+    # Events are always async
+    if re.match("^IPA.*EventInterface$", method.interface.mojom_name):
+        return True
+    elif re.match("^IPA.*Interface$", method.interface.mojom_name):
+        if method.attributes is None:
+            return False
+        elif 'async' in method.attributes and method.attributes['async']:
+            return True
+    return False
+
+def IsArray(element):
+    return mojom.IsArrayKind(element.kind)
+
+def IsControls(element):
+    return mojom.IsStructKind(element.kind) and (element.kind.mojom_name == "ControlList" or
+                                                 element.kind.mojom_name == "ControlInfoMap")
+
+def IsEnum(element):
+    return mojom.IsEnumKind(element.kind)
+
+def IsFd(element):
+    return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "FileDescriptor"
+
+def IsMap(element):
+    return mojom.IsMapKind(element.kind)
+
+def IsPlainStruct(element):
+    return mojom.IsStructKind(element.kind) and not IsControls(element) and not IsFd(element)
+
+def IsPod(element):
+    return element.kind in _kind_to_cpp_type
+
+def IsStr(element):
+    return element.kind.spec == 's'
+
+def BitWidth(element):
+    if element.kind in _bit_widths:
+        return _bit_widths[element.kind]
+    if mojom.IsEnumKind(element.kind):
+        return '32'
+    return ''
+
+# Get the type name for a given element
+def GetNameForElement(element):
+    # structs
+    if (mojom.IsEnumKind(element) or
+        mojom.IsInterfaceKind(element) or
+        mojom.IsStructKind(element)):
+        return element.mojom_name
+    # vectors
+    if (mojom.IsArrayKind(element)):
+        elem_name = GetNameForElement(element.kind)
+        return f'std::vector<{elem_name}>'
+    # maps
+    if (mojom.IsMapKind(element)):
+        key_name = GetNameForElement(element.key_kind)
+        value_name = GetNameForElement(element.value_kind)
+        return f'std::map<{key_name}, {value_name}>'
+    # struct fields and function parameters
+    if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)):
+        # maps and vectors
+        if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)):
+            return GetNameForElement(element.kind)
+        # strings
+        if (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'):
+            return 'std::string'
+        # PODs
+        if element.kind in _kind_to_cpp_type:
+            return _kind_to_cpp_type[element.kind]
+        # structs and enums
+        return element.kind.mojom_name
+    # PODs that are members of vectors/maps
+    if (hasattr(element, '__hash__') and element in _kind_to_cpp_type):
+        return _kind_to_cpp_type[element]
+    if (hasattr(element, 'spec')):
+        # strings that are members of vectors/maps
+        if (element.spec == 's'):
+            return 'std::string'
+        # structs that aren't defined in mojom that are members of vectors/maps
+        if (element.spec[0] == 'x'):
+            return element.spec.replace('x:', '').replace('.', '::')
+    if (mojom.IsInterfaceRequestKind(element) or
+        mojom.IsAssociatedKind(element) or
+        mojom.IsPendingRemoteKind(element) or
+        mojom.IsPendingReceiverKind(element) or
+        mojom.IsUnionKind(element)):
+        raise Exception('Unsupported element: %s' % element)
+    raise Exception('Unexpected element: %s' % element)
+
+def GetFullNameForElement(element, namespace_str):
+    name = GetNameForElement(element)
+    if namespace_str == '':
+        return name
+    return f'{namespace_str}::{name}'
+
+def ValidateZeroLength(l, s, cap=True):
+    if l is None:
+        return
+    if len(l) > 0:
+        raise Exception(f'{s.capitalize() if cap else s} should be empty')
+
+def ValidateSingleLength(l, s, cap=True):
+    if len(l) > 1:
+        raise Exception(f'Only one {s} allowed')
+    if len(l) < 1:
+        raise Exception(f'{s.capitalize() if cap else s} is required')
+
+def GetMainInterface(interfaces):
+    intf = [x for x in interfaces
+            if re.match("^IPA.*Interface", x.mojom_name) and
+               not re.match("^IPA.*EventInterface", x.mojom_name)]
+    ValidateSingleLength(intf, 'main interface')
+    return None if len(intf) == 0 else intf[0]
+
+def GetEventInterface(interfaces):
+    event = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)]
+    ValidateSingleLength(event, 'event interface')
+    return None if len(event) == 0 else event[0]
+
+def ValidateNamespace(namespace):
+    if namespace == '':
+        raise Exception('Must have a namespace')
+
+    if not re.match('^ipa\.[0-9A-Za-z_]+', namespace):
+        raise Exception('Namespace must be of the form "ipa.{pipeline_name}"')
+
+def ValidateInterfaces(interfaces):
+    # Validate presence of main interface
+    intf = GetMainInterface(interfaces)
+    if intf is None:
+        raise Exception('Must have main IPA interface')
+
+    # Validate presence of event interface
+    event = GetEventInterface(interfaces)
+    if intf is None:
+        raise Exception('Must have event IPA interface')
+
+    # Validate required main interface functions
+    f_init  = [x for x in intf.methods if x.mojom_name == 'init']
+    f_start = [x for x in intf.methods if x.mojom_name == 'start']
+    f_stop  = [x for x in intf.methods if x.mojom_name == 'stop']
+
+    ValidateSingleLength(f_init, 'init()', False)
+    ValidateSingleLength(f_start, 'start()', False)
+    ValidateSingleLength(f_stop, 'stop()', False)
+
+    f_init  = f_init[0]
+    f_start = f_start[0]
+    f_stop  = f_stop[0]
+
+    # Validate parameters to init()
+    ValidateSingleLength(f_init.parameters, 'input parameter to init()')
+    ValidateSingleLength(f_init.response_parameters, 'output parameter from init()')
+    if f_init.parameters[0].kind.mojom_name != 'IPASettings':
+        raise Exception('init() must have single IPASettings input parameter')
+    if f_init.response_parameters[0].kind.spec != 'i32':
+        raise Exception('init() must have single int32 output parameter')
+
+    # No need to validate start() as it is customizable
+
+    # Validate parameters to stop()
+    ValidateZeroLength(f_stop.parameters, 'input parameter to stop()')
+    ValidateZeroLength(f_stop.parameters, 'output parameter from stop()')
+
+    # Validate that event interface has at least one event
+    if len(event.methods) < 1:
+        raise Exception('Event interface must have at least one event')
+
+    # Validate that all async methods don't have return values
+    intf_methods_async = [x for x in intf.methods if IsAsync(x)]
+    for method in intf_methods_async:
+        ValidateZeroLength(method.response_parameters,
+                           f'{method.mojom_name} response parameters', False)
+
+    event_methods_async = [x for x in event.methods if IsAsync(x)]
+    for method in event_methods_async:
+        ValidateZeroLength(method.response_parameters,
+                           f'{method.mojom_name} response parameters', False)
+
+class Generator(generator.Generator):
+    @staticmethod
+    def GetTemplatePrefix():
+        return 'libcamera_templates'
+
+    def GetFilters(self):
+        libcamera_filters = {
+            'all_types': GetAllTypes,
+            'bit_width': BitWidth,
+            'cap': Capitalize,
+            'choose': Choose,
+            'comma_sep': CommaSep,
+            'default_value': GetDefaultValue,
+            'has_default_fields': HasDefaultFields,
+            'has_fd': HasFd,
+            'is_async': IsAsync,
+            'is_array': IsArray,
+            'is_controls': IsControls,
+            'is_enum': IsEnum,
+            'is_fd': IsFd,
+            'is_map': IsMap,
+            'is_plain_struct': IsPlainStruct,
+            'is_pod': IsPod,
+            'is_str': IsStr,
+            'method_input_has_fd': MethodInputHasFd,
+            'method_output_has_fd': MethodOutputHasFd,
+            'method_param_names': MethodParamNames,
+            'method_param_inputs': MethodParamInputs,
+            'method_param_outputs': MethodParamOutputs,
+            'method_parameters': MethodParameters,
+            'method_return_value': MethodReturnValue,
+            'name': GetNameForElement,
+            'name_full': GetFullNameForElement,
+            'needs_control_serializer': NeedsControlSerializer,
+            'params_comma_sep': ParamsCommaSep,
+            'with_default_values': WithDefaultValues,
+            'with_fds': WithFds,
+        }
+        return libcamera_filters
+
+    def _GetJinjaExports(self):
+        return {
+            'cmd_enum_name': '_%sCmd' % self.module_name,
+            'cmd_event_enum_name': '_%sEventCmd' % self.module_name,
+            'consts': self.module.constants,
+            'enums': self.module.enums,
+            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
+            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
+            'has_namespace': self.module.mojom_namespace != '',
+            'interface_event': GetEventInterface(self.module.interfaces),
+            'interface_main': GetMainInterface(self.module.interfaces),
+            'interface_name': 'IPA%sInterface' % self.module_name,
+            'module_name': ModuleName(self.module.path),
+            'namespace': self.module.mojom_namespace.split('.'),
+            'namespace_str': self.module.mojom_namespace.replace('.', '::') if
+                             self.module.mojom_namespace is not None else '',
+            'proxy_name': 'IPAProxy%s' % self.module_name,
+            'proxy_worker_name': 'IPAProxy%sWorker' % self.module_name,
+            'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0],
+        }
+
+    def _GetJinjaExportsForCore(self):
+        return {
+            'consts': self.module.constants,
+            'enums': self.module.enums,
+            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
+            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
+            'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes],
+            'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes],
+        }
+
+    @UseJinja('core_ipa_interface.h.tmpl')
+    def _GenerateCoreHeader(self):
+        return self._GetJinjaExportsForCore()
+
+    @UseJinja('core_ipa_serializer.h.tmpl')
+    def _GenerateCoreSerializer(self):
+        return self._GetJinjaExportsForCore()
+
+    @UseJinja('module_ipa_interface.h.tmpl')
+    def _GenerateDataHeader(self):
+        return self._GetJinjaExports()
+
+    @UseJinja('module_ipa_serializer.h.tmpl')
+    def _GenerateSerializer(self):
+        return self._GetJinjaExports()
+
+    @UseJinja('module_ipa_proxy.cpp.tmpl')
+    def _GenerateProxyCpp(self):
+        return self._GetJinjaExports()
+
+    @UseJinja('module_ipa_proxy.h.tmpl')
+    def _GenerateProxyHeader(self):
+        return self._GetJinjaExports()
+
+    @UseJinja('module_ipa_proxy_worker.cpp.tmpl')
+    def _GenerateProxyWorker(self):
+        return self._GetJinjaExports()
+
+    def GenerateFiles(self, unparsed_args):
+        parser = argparse.ArgumentParser()
+        parser.add_argument('--libcamera_generate_core_header',     action='store_true')
+        parser.add_argument('--libcamera_generate_core_serializer', action='store_true')
+        parser.add_argument('--libcamera_generate_header',          action='store_true')
+        parser.add_argument('--libcamera_generate_serializer',      action='store_true')
+        parser.add_argument('--libcamera_generate_proxy_cpp',       action='store_true')
+        parser.add_argument('--libcamera_generate_proxy_h',         action='store_true')
+        parser.add_argument('--libcamera_generate_proxy_worker',    action='store_true')
+        parser.add_argument('--libcamera_output_path')
+        args = parser.parse_args(unparsed_args)
+
+        if not args.libcamera_generate_core_header and \
+           not args.libcamera_generate_core_serializer:
+            ValidateNamespace(self.module.mojom_namespace)
+            ValidateInterfaces(self.module.interfaces)
+            self.module_name = ModuleClassName(self.module)
+
+        fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path))
+
+        gen_funcs = [
+                [args.libcamera_generate_core_header,     self._GenerateCoreHeader],
+                [args.libcamera_generate_core_serializer, self._GenerateCoreSerializer],
+                [args.libcamera_generate_header,          self._GenerateDataHeader],
+                [args.libcamera_generate_serializer,      self._GenerateSerializer],
+                [args.libcamera_generate_proxy_cpp,       self._GenerateProxyCpp],
+                [args.libcamera_generate_proxy_h,         self._GenerateProxyHeader],
+                [args.libcamera_generate_proxy_worker,    self._GenerateProxyWorker],
+        ]
+
+        for pair in gen_funcs:
+            if pair[0]:
+                self.Write(pair[1](), args.libcamera_output_path)