summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmelle Laine <armellel@google.com>2022-05-27 07:39:42 +0000
committerArmelle Laine <armellel@google.com>2022-06-20 17:01:11 +0000
commitb44d4c5aefea7f78422870c50a9e7cc1ce0f2a29 (patch)
treed57d16c41b3a74af0cfd1dd7415cc41197e14fb9
parent3228ab6c5530343fde92e14e963135c890daae13 (diff)
downloadaosp-for/refs/master.tar.gz
Add a python tool generating a c wrapper on top of aidl clientsfor/refs/master
Support trusty_user and ql_tipc domains Bug: 234584308 Change-Id: If136588fc7b8b408d2c2e480d009e8c68d9ba2e0
-rw-r--r--scripts/aidl/aidl_to_c.py186
-rw-r--r--scripts/aidl/c_wrapper/__init__.py3
-rw-r--r--scripts/aidl/c_wrapper/iface_h2c.py337
-rw-r--r--scripts/aidl/c_wrapper/tpl/client_c.j269
-rw-r--r--scripts/aidl/c_wrapper/tpl/client_h.j270
-rw-r--r--scripts/aidl/c_wrapper/tpl/macros/c_connect_fn.j2134
-rw-r--r--scripts/aidl/c_wrapper/tpl/macros/c_method_fn.j2198
7 files changed, 997 insertions, 0 deletions
diff --git a/scripts/aidl/aidl_to_c.py b/scripts/aidl/aidl_to_c.py
new file mode 100644
index 0000000..99ace73
--- /dev/null
+++ b/scripts/aidl/aidl_to_c.py
@@ -0,0 +1,186 @@
+#!/bin/sh
+"." "`dirname $0`/envsetup.sh"
+"exec" "$PY3" "$0" "$@"
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Generate from an aidl-generated interface header, its client c wrapper."""
+
+from pathlib import Path
+import argparse
+import subprocess
+import glob
+from c_wrapper import iface_to_c
+
+script_dir = Path(__file__).parent
+root_dir = script_dir / "../../../../../.."
+
+
+def run_aidl(aidl_file, aidl_tool_str: str, out_cpp: str, out_hdr: str):
+ aidl_tool = Path(aidl_tool_str)
+ if not aidl_tool.exists():
+ raise (
+ "aidl tool cannot be found at {}, please build qemu-generic-64 target\n".format(
+ aidl_tool.resolve()
+ )
+ )
+ aidl_path = Path(aidl_file)
+ completed_process = subprocess.run(
+ " ".join(
+ [
+ aidl_tool.as_posix(),
+ "--lang=trusty",
+ "-h",
+ out_hdr,
+ "-o",
+ out_cpp,
+ aidl_path.as_posix(),
+ ]
+ ),
+ shell=True,
+ text=True,
+ capture_output=True,
+ )
+ if completed_process.returncode:
+ raise Exception(completed_process.stderr)
+ return Path(out_hdr) / "{}.h".format(aidl_path.stem)
+
+
+def run_clang_format(d):
+ files = []
+ for f in glob.glob("{}/*.cpp".format(d)):
+ files.append(f)
+ for f in glob.glob("{}/*.h".format(d)):
+ files.append(f)
+ for f in files:
+ completed_process = subprocess.run(
+ " ".join(
+ [
+ "clang-format",
+ "-i",
+ f,
+ ]
+ ),
+ shell=True,
+ text=True,
+ capture_output=True,
+ )
+ if completed_process.returncode:
+ raise Exception(completed_process.stderr)
+
+
+def main(*cmd_line):
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "INPUT",
+ type=str,
+ help="An AIDL-generated interface header file or an AIDL file.",
+ )
+ parser.add_argument(
+ "--domain",
+ type=str,
+ choices=["ql_tipc", "trusty_user"],
+ help="c client domain.",
+ )
+ parser.add_argument(
+ "--bn",
+ action="store_true",
+ help="Bn (server) mode - do not generate Bp C wrapper.",
+ )
+ parser.add_argument(
+ "--append-cpp",
+ action="store_true",
+ help="append generated C code (Bp C wrapper) into the interface's cpp.",
+ )
+ parser.add_argument(
+ "--dbg",
+ action="store_true",
+ help="debug mode - stores ast and methods json files.",
+ )
+ parser.add_argument(
+ "--out", type=str, help="base output directory for generated files"
+ )
+ parser.add_argument(
+ "--header",
+ type=str,
+ help="header output directory for the generated header files.",
+ )
+ parser.add_argument(
+ "--aidl-tool",
+ type=str,
+ help="aidl tool path.",
+ )
+ if len(cmd_line) > 0:
+ args = parser.parse_args(cmd_line)
+ else:
+ args = parser.parse_args()
+ file_input = Path(args.INPUT)
+ iface_name = file_input.stem
+ if file_input.suffix == ".h":
+ iface_hdr = Path(args.header) / "{}.h".format(iface_name)
+ elif file_input.suffix == ".aidl":
+ iface_hdr = run_aidl(
+ file_input.as_posix(), args.aidl_tool, args.out, args.header
+ )
+ else:
+ raise Exception(
+ "input file `{}` not supported header, please specify with a .h or .aidl file".format(
+ file_input.as_posix()
+ )
+ )
+ if not iface_hdr.exists():
+ raise Exception(
+ "interface header `{}` not found, please run aidl tool".format(
+ iface_hdr.as_posix()
+ )
+ )
+ iface_to_c(
+ iface_hdr,
+ args.domain,
+ args.out,
+ args.header,
+ bn=args.bn,
+ append_cpp=args.append_cpp,
+ dbg=args.dbg,
+ )
+ for d in [args.out, args.header]:
+ run_clang_format(d)
+
+
+def example():
+ # example command lines for generating trusty_user and ql_tipc flavors
+ for aidl_file in [
+ root_dir / "trusty/user/base/interface/boot_done/IBootDone.aidl",
+ ]:
+ aidl_dir = aidl_file.parent
+ for domain in ["trusty_user", "ql_tipc"]:
+ generated = "generated"
+ if domain == "ql_tipc":
+ generated += "_ql_tipc"
+ main(
+ "--domain",
+ domain,
+ "--out",
+ (aidl_dir / generated).as_posix(),
+ "--header",
+ (aidl_dir / generated / "include").as_posix(),
+ "--aidl-tool",
+ (root_dir / "trusty/prebuilts/aosp/tidl/tidl").as_posix(),
+ aidl_file.as_posix(),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/aidl/c_wrapper/__init__.py b/scripts/aidl/c_wrapper/__init__.py
new file mode 100644
index 0000000..6eb31c6
--- /dev/null
+++ b/scripts/aidl/c_wrapper/__init__.py
@@ -0,0 +1,3 @@
+from .iface_h2c import iface_to_c
+
+__all__ = [iface_to_c]
diff --git a/scripts/aidl/c_wrapper/iface_h2c.py b/scripts/aidl/c_wrapper/iface_h2c.py
new file mode 100644
index 0000000..d0b3a3b
--- /dev/null
+++ b/scripts/aidl/c_wrapper/iface_h2c.py
@@ -0,0 +1,337 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Generate a client (proxy) in c, as a wrapper on top of the aidl-generated
+binder proxy cpp class.
+
+Requires the aidl-generated interface header as input
+"""
+import functools
+from jinja2 import FileSystemLoader, Environment
+import json
+from pathlib import Path
+import re
+import subprocess
+from typing import List
+
+script_dir = Path(__file__).parent
+root_dir = script_dir / "../../../../../../.."
+aidl_tool = root_dir / "trusty/prebuilts/aosp/tidl/tidl"
+
+jinja_template_loader = FileSystemLoader(searchpath=script_dir / "tpl")
+jinja_template_env = Environment(
+ loader=jinja_template_loader,
+ trim_blocks=True,
+ lstrip_blocks=True,
+ keep_trailing_newline=True,
+ line_comment_prefix="###",
+)
+
+
+class Consts:
+ def __init__(self, const_list: List):
+ self.consts = const_list
+
+ @functools.cached_property
+ def ordered_strings(self):
+ return sorted(
+ [s for s in self.consts if s["qualType"].find("const char [") == 0],
+ key=lambda s: s["name"],
+ )
+
+ @functools.cached_property
+ def ordered_positive_integers(self):
+ return sorted(
+ [
+ s
+ for s in self.consts
+ if (s["qualType"].find("const int32_t") == 0) and (s["value"][0] != "-")
+ ],
+ key=lambda s: s["value"],
+ )
+
+ @functools.cached_property
+ def ordered_negative_integers(self):
+ return sorted(
+ [
+ s
+ for s in self.consts
+ if (s["qualType"].find("const int32_t") == 0) and (s["value"][0] == "-")
+ ],
+ key=lambda s: s["value"],
+ )
+
+ @functools.cached_property
+ def ordered_error_integers(self):
+ return sorted(
+ [
+ s
+ for s in self.consts
+ if (s["qualType"].find("const int32_t") == 0)
+ and (s["name"].find("ERR") > -1)
+ ],
+ key=lambda s: s["value"],
+ )
+
+ @staticmethod
+ def len(ll: List):
+ return len(ll)
+
+
+def clang_ast(hdr_file: Path, filter_name: str, domain: str):
+ completed_process = subprocess.run(
+ " ".join(
+ [
+ "clang++",
+ "-I{}".format(hdr_file.parent),
+ "-I{}".format(
+ root_dir / "trusty/user/base/experimental/lib/binder-paidl/include"
+ ),
+ " ".join(
+ [
+ "-I{}".format(
+ root_dir / "external/trusty/bootloader/ql-tipc/include"
+ ),
+ "-I{}".format(
+ root_dir
+ / "external/trusty/bootloader/ql-tipc/include/trusty/sysdeps/aarch64"
+ ),
+ "-I{}".format(root_dir / "external/lk/include/uapi"),
+ "-I{}".format(root_dir / "external/lk/include/shared"),
+ "-I{}".format(root_dir / "external/lk/lib/libc/include"),
+ ]
+ )
+ if domain == "ql_tipc"
+ else " ".join(
+ [
+ "-I{}".format(root_dir / "external/lk/include/uapi"),
+ "-I{}".format(root_dir / "external/lk/include/shared"),
+ "-I{}".format(root_dir / "trusty/kernel/include/uapi"),
+ "-I{}".format(root_dir / "trusty/kernel/include/shared"),
+ "-I{}".format(root_dir / "trusty/user/base/lib/tipc/include"),
+ "-I{}".format(root_dir / "trusty/user/base/include/user"),
+ ]
+ ),
+ "-UANDROID",
+ "-D__TRUSTY__",
+ "-D__QL_TIPC__" if domain == "ql_tipc" else "",
+ "-std=c++17",
+ "-Xclang",
+ "-ast-dump=json",
+ "-Xclang",
+ "-ast-dump-filter",
+ "-Xclang",
+ filter_name,
+ "-fsyntax-only",
+ "-fno-color-diagnostics",
+ "-Wno-visibility",
+ hdr_file.as_posix(),
+ ]
+ ),
+ shell=True,
+ text=True,
+ capture_output=True,
+ )
+ if completed_process.returncode:
+ print(completed_process.stderr)
+
+ blobs = re.split("Dumping.*\n", completed_process.stdout)
+ ast = []
+ for b in blobs:
+ if len(b) == 0:
+ continue
+ ast.append(json.loads(b))
+ return ast
+
+
+def ast_parse_arg(item):
+ def get_spec(arg):
+ m = re.match(r"std::array<([^,]*), ([^\>]*)>", arg["qualType"])
+ if m is not None:
+ (type, size) = m.groups()
+ return dict(isInput=True, isArray=True, arrayType=type, arraySize=size)
+
+ m = re.match(r"::trusty::aidl::FixedPayload<([^\>]*)>", arg["qualType"])
+ if m is not None:
+ (size,) = m.groups()
+ return dict(isInput=True, isArray=True, arrayType="uint8_t", arraySize=size)
+
+ m = re.match(r"::trusty::aidl::Payload.*\*", arg["qualType"])
+ if m is not None:
+ return dict(isInput=False, isArray=False, isPayload=True)
+
+ m = re.match(r"::trusty::aidl::Payload.*\&", arg["qualType"])
+ if m is not None:
+ return dict(isInput=True, isArray=False, isPayload=True)
+
+ if arg["qualType"][-1] == "*":
+ return dict(isInput=False, isArray=False, isPayload=False)
+
+ return dict(isInput=True, isArray=False, isPayload=False)
+
+ if item["kind"] != "ParmVarDecl":
+ return None
+ if "name" not in item:
+ return None
+ arg = dict(name=item["name"].replace("arg_", ""), qualType=item["type"]["qualType"])
+ arg["spec"] = get_spec(arg)
+ return arg
+
+
+def ast_parse_field(item):
+ if item["kind"] != "FieldDecl":
+ return None
+ if "name" not in item:
+ return None
+ field = dict(name=item["name"], qualType=item["type"]["qualType"])
+ return field
+
+
+def ast_parse_method(item):
+ return dict(
+ name=item["name"],
+ args=list(filter(None, [ast_parse_arg(arg) for arg in item["inner"]]))
+ if "inner" in item
+ else [],
+ )
+
+
+def ast_parse_struct(item):
+ return dict(
+ name=item["name"],
+ fields=list(filter(None, [ast_parse_field(field) for field in item["inner"]]))
+ if "inner" in item
+ else [],
+ )
+
+
+def ast_parse_const(item):
+ def get_literal_value(item):
+ if item["inner"][0]["kind"] in ["StringLiteral", "IntegerLiteral"]:
+ return item["inner"][0]["value"]
+ if item["inner"][0]["kind"] in ["UnaryOperator"]:
+ return item["inner"][0]["opcode"] + get_literal_value(item["inner"][0])
+ raise Exception("Cannot parse item {}item".format(item))
+
+ try:
+ return dict(
+ name=item["name"],
+ value=get_literal_value(item),
+ qualType=item["type"]["qualType"],
+ )
+ except Exception as e:
+ print(item)
+
+
+def ast_cross_ref(iface_name, methods, structs):
+ for method in methods:
+ for arg in method["args"]:
+ m = re.match(
+ ".*{}::([^:\s]*)\s+[\*\&]+$".format(iface_name), arg["qualType"]
+ )
+ if m is None:
+ continue
+ (struct,) = m.groups()
+ for s in structs:
+ if s["name"] == struct:
+ arg["xref"] = s
+
+
+def c_client_wrapper(iface_ast, iface_name, impl_name, c_impl_name, domain):
+ methods = []
+ structs = []
+ consts = []
+ for ast in iface_ast:
+ if ast["name"] != iface_name:
+ continue
+ if ast["kind"] != "CXXRecordDecl":
+ raise Exception(
+ "iface {} shall be defined as a CXXRecordDecl".format(iface_name)
+ )
+ if ast["tagUsed"] != "class":
+ raise Exception("iface {} shall be defined as a class".format(iface_name))
+ for item in ast["inner"]:
+ if item["kind"] == "CXXMethodDecl":
+ if "isImplicit" in item and item["isImplicit"]:
+ continue # C++ operators
+ if "pure" not in item or not item["pure"]:
+ continue # no need to implement in c the non-pure methods
+ # as they are inherited by the base class Binder
+ methods.append(ast_parse_method(item))
+ if item["kind"] == "CXXRecordDecl" and item["tagUsed"] == "struct":
+ structs.append(ast_parse_struct(item))
+ if item["kind"] == "VarDecl" and item["constexpr"] == True:
+ consts.append(ast_parse_const(item))
+ ast_cross_ref(iface_name, methods, structs)
+ tm = jinja_template_env.get_template("client_c.j2")
+ tm_env = dict(
+ iface_name=iface_name,
+ impl_name=impl_name,
+ c_impl_name=c_impl_name,
+ methods=methods,
+ structs=structs,
+ consts=Consts(consts),
+ domain=domain,
+ )
+ c = tm.render(**tm_env)
+
+ tm = jinja_template_env.get_template("client_h.j2")
+ h = tm.render(**tm_env)
+ return dict(c=c, h=h, methods=methods, structs=structs)
+
+
+def iface_to_c(
+ iface_hdr: Path,
+ domain: str,
+ out_cpp: str,
+ out_hdr: str,
+ bn=False,
+ append_cpp=False,
+ dbg=False,
+):
+ if domain not in ["trusty_user", "ql_tipc"]:
+ raise Exception("domain `{}` not supported", domain)
+ iface_name = iface_hdr.stem
+ impl_name = iface_name[1:]
+ iface_ast = clang_ast(iface_hdr, iface_name, domain)
+ # bp_ast = clang_ast(args.bp_cpp)
+ out_cpp_path = Path(out_cpp)
+ out_cpp_path.mkdir(parents=True, exist_ok=True)
+ out_hdr_path = Path(out_hdr)
+ out_hdr_path.mkdir(parents=True, exist_ok=True)
+ if dbg:
+ with open(out_cpp_path / "{}_ast.json".format(iface_name), "w") as f:
+ json.dump(iface_ast, f, indent=2)
+
+ # convert impl name from camelCase to snake_case
+ c_impl_name = re.sub(r"(?<!^)(?=[A-Z])", "_", impl_name).lower()
+ c_client = c_client_wrapper(iface_ast, iface_name, impl_name, c_impl_name, domain)
+ if dbg:
+ with open(out_cpp_path / "{}_methods.json".format(c_impl_name), "w") as f:
+ json.dump(
+ dict(methods=c_client["methods"], structs=c_client["structs"]),
+ f,
+ indent=2,
+ )
+ if not bn:
+ if append_cpp:
+ with open(out_cpp_path / "{}.cpp".format(iface_name), "a") as f:
+ f.write(c_client["c"])
+ else:
+ with open(out_cpp_path / "{}_CBp.cpp".format(iface_name), "w") as f:
+ f.write(c_client["c"])
+
+ with open(out_hdr_path / "{}.h".format(c_impl_name), "w") as f:
+ f.write(c_client["h"])
diff --git a/scripts/aidl/c_wrapper/tpl/client_c.j2 b/scripts/aidl/c_wrapper/tpl/client_c.j2
new file mode 100644
index 0000000..8251de9
--- /dev/null
+++ b/scripts/aidl/c_wrapper/tpl/client_c.j2
@@ -0,0 +1,69 @@
+{% import 'macros/c_method_fn.j2' as method_fn %}
+{% import 'macros/c_connect_fn.j2' as connect_fn %}
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define TLOG_TAG "{{c_impl_name}}_client"
+
+{% if domain == 'trusty_user' %}
+#include <assert.h>
+#include <lib/tipc/tipc.h>
+#include <string.h>
+#include <trusty_log.h>
+#include <uapi/err.h>
+{% elif domain == 'ql_tipc' %}
+#include <trusty/sysdeps.h>
+#include <trusty/trusty_log.h>
+#include <uapi/err.h>
+{% endif %}
+
+#include <Bp{{impl_name}}.h>
+{% if consts.len(consts.ordered_error_integers) > 0 %}
+#include <{{c_impl_name}}.h>
+{% else %}
+#include <lib/binder/lk_strerror.h>
+{% endif %}
+
+{{connect_fn.connect_impl(consts, c_impl_name, impl_name, domain)}}
+{{connect_fn.connect_teardown(c_impl_name, impl_name, domain)}}
+{% for method in methods %}
+{{method_fn.comment(c_impl_name, method)}}
+{{method_fn.signature(c_impl_name, method, hdr=false)}} {
+{{connect_fn.connect_invoke(c_impl_name, impl_name, domain)}}
+{% for arg in method.args %}
+{{ method_fn.arg_prolog(iface_name, domain, arg) }}
+{%- endfor %}
+ rc = {{c_impl_name}}->{{method.name}}(
+{% for arg in method.args %}
+{{ method_fn.arg_invoke(arg) }} {{ "," if not loop.last else "" }}
+{%- endfor %}
+ );
+ if (rc != android::OK) {
+{% if consts.len(consts.ordered_error_integers) > 0 %}
+ TLOGE("{{method.name}} failed - %s(%d).\n", {{c_impl_name}}_strerror(rc), rc);
+{% else %}
+ TLOGE("{{method.name}} failed - %s(%d).\n", lk_strerror(rc), rc);
+{% endif %}
+ return rc;
+ }
+{% for arg in method.args %}
+{{ method_fn.arg_epilog(arg) }}
+{%- endfor %}
+ return rc;
+}
+{{''}}
+{{''}}
+{%- endfor %}
diff --git a/scripts/aidl/c_wrapper/tpl/client_h.j2 b/scripts/aidl/c_wrapper/tpl/client_h.j2
new file mode 100644
index 0000000..e23e26c
--- /dev/null
+++ b/scripts/aidl/c_wrapper/tpl/client_h.j2
@@ -0,0 +1,70 @@
+{% import 'macros/c_connect_fn.j2' as connect_fn %}
+{% import 'macros/c_method_fn.j2' as method_fn %}
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+{% if domain == "trusty_user" %}
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+{% elif domain == "ql_tipc" %}
+#include <trusty/sysdeps.h>
+{% endif %}
+{% if consts.len(consts.ordered_error_integers) > 0 %}
+#include <lib/binder/lk_strerror.h>
+{% endif %}
+
+#pragma once
+
+{% for s in consts.ordered_strings %}
+#define {{c_impl_name.upper()}}_{{s["name"]}} {{s["value"]}}
+{% endfor %}
+{{'' if consts.len(consts.ordered_strings) > 0}}
+{% for s in consts.ordered_positive_integers %}
+#define {{s["name"]}} {{s["value"]}} /* 0x{{ '%08x' % s["value"] | int }} */
+{% endfor %}
+{{'' if consts.len(consts.ordered_positive_integers) > 0}}
+{% for s in consts.ordered_negative_integers %}
+#define {{s["name"]}} {{s["value"]}} /* -0x{{ '%08x' % s["value"] | int | abs}} */
+{% endfor %}
+{{'' if consts.len(consts.ordered_negative_integers) > 0}}
+
+__BEGIN_CDECLS
+
+{{connect_fn.connect_hdr(c_impl_name, impl_name, domain)}}
+{% for method in methods %}
+{{method_fn.comment(c_impl_name, method)}}
+{{method_fn.signature(c_impl_name, method, hdr=true)}};
+{{''}}
+{{''}}
+{%- endfor %}
+
+{% if consts.len(consts.ordered_error_integers) > 0 %}
+static __ALWAYS_INLINE char * {{c_impl_name}}_strerror(int errnum) {
+ switch (errnum)
+ {
+{% for err in consts.ordered_error_integers %}
+ case {{err.name}}:
+ return (char *)"{{err.name}}";
+{% endfor %}
+ default:
+ return lk_strerror(errnum);
+ }
+}
+{% endif %}
+
+__END_CDECLS
diff --git a/scripts/aidl/c_wrapper/tpl/macros/c_connect_fn.j2 b/scripts/aidl/c_wrapper/tpl/macros/c_connect_fn.j2
new file mode 100644
index 0000000..e5fe4c6
--- /dev/null
+++ b/scripts/aidl/c_wrapper/tpl/macros/c_connect_fn.j2
@@ -0,0 +1,134 @@
+### *****************
+### Connect Header
+### *****************
+
+{% macro connect_hdr(c_impl_name, impl_name, domain) %}
+{% if domain == "ql_tipc" %}
+/**
+ * {{c_impl_name}}_connect() - connect to the interface port
+ * and initialize the global pointer {{c_impl_name}}.
+ *
+ * Requires invocation of {{c_impl_name}}_shutdown()
+ * for proper teardown.
+ * @Param port: port to which to connect to
+ * @return: 0 on success, or an error code < 0 on failure.
+ */
+int {{c_impl_name}}_connect(const char* port);
+
+/**
+ * {{c_impl_name}}_shutdown() - shutdown the interface client
+ *
+ * Destroys the global pointer of the interface client {{c_impl_name}}.
+ *
+ */
+void {{c_impl_name}}_shutdown(void);
+{% endif %}
+{% endmacro %}
+
+### *************************
+### Connect Implementation
+### *************************
+
+{% macro connect_impl(consts, c_impl_name, impl_name, domain) %}
+{% if domain == "trusty_user" %}
+/**
+ * {{c_impl_name}}_connect()
+ * @param {{c_impl_name}} reference to the binder proxy instance
+ *
+ * Return: 0 on success, or an error code < 0 on failure.
+ */
+int {{c_impl_name}}_connect(std::optional<aidl::Bp{{impl_name}}>& {{c_impl_name}}) {
+ int rc;
+ rc = aidl::Bp{{impl_name}}::connect(
+ {{c_impl_name}},
+ aidl::I{{impl_name}}::PORT,
+ IPC_CONNECT_WAIT_FOR_PORT
+ );
+ if (rc < 0) {
+ TLOGE("Failed to connect to %s: %d\n", aidl::I{{impl_name}}::PORT, rc);
+ return rc;
+ }
+ assert({{c_impl_name}}.has_value());
+ return rc;
+}
+{% elif domain == "ql_tipc" %}
+
+aidl::Bp{{impl_name}}* {{c_impl_name}} = (aidl::Bp{{impl_name}}*)NULL;
+
+{% for s in consts.ordered_strings %}
+constexpr char aidl::I{{impl_name}}::{{s["name"]}}[];
+{% endfor %}
+
+/**
+ * {{c_impl_name}}_connect() - connect to the interface port
+ * and initialize the global pointer {{c_impl_name}}.
+ *
+ * Requires invocation of {{c_impl_name}}_shutdown()
+ * for proper teardown.
+ * @param: port to which to connect to,
+ * if port is NULL, use the default API's port
+ * Return: 0 on success, or an error code < 0 on failure.
+ */
+extern "C" int {{c_impl_name}}_connect(const char* port) {
+ int rc;
+ if ({{c_impl_name}}) {
+ return NO_ERROR;
+ }
+ if (port) {
+ rc = aidl::Bp{{impl_name}}::connect({{c_impl_name}}, port, 0);
+ } else {
+ rc = aidl::Bp{{impl_name}}::connect(
+ {{c_impl_name}}, aidl::I{{impl_name}}::PORT, 0
+ );
+ }
+ if (rc < 0) {
+ TLOGE("Failed to connect to %s: %d\n", aidl::I{{impl_name}}::PORT, rc);
+ return rc;
+ }
+ assert({{c_impl_name}});
+ return rc;
+}
+{% endif %}
+{% endmacro %}
+
+### ******************************************
+### Connect teardown: only needed on ql_tipc
+### ******************************************
+
+{% macro connect_teardown(c_impl_name, impl_name, domain) %}
+{% if domain == "ql_tipc" %}
+/**
+ * {{c_impl_name}}_shutdown() - shutdown the interface client
+ *
+ * Destroy the global pointer of the interface client {{c_impl_name}}.
+ *
+ */
+extern "C" void {{c_impl_name}}_shutdown(void) {
+ if ({{c_impl_name}}) {
+ delete {{c_impl_name}};
+ {{c_impl_name}} = (aidl::Bp{{impl_name}}*)NULL;
+ }
+}
+{% endif %}
+{% endmacro %}
+
+### *****************
+### Connect invoke
+### *****************
+
+{% macro connect_invoke(c_impl_name, impl_name, domain) %}
+{% if domain == "trusty_user" %}
+ int rc;
+ std::optional<aidl::Bp{{impl_name}}> {{c_impl_name}};
+ rc = {{c_impl_name}}_connect({{c_impl_name}});
+ if (rc < 0) {
+ return rc;
+ }
+{% endif %}
+{% if domain == "ql_tipc" %}
+ int rc;
+ if (!{{c_impl_name}}) {
+ return ERR_BAD_STATE;
+ }
+{% endif %}
+{%- endmacro %}
diff --git a/scripts/aidl/c_wrapper/tpl/macros/c_method_fn.j2 b/scripts/aidl/c_wrapper/tpl/macros/c_method_fn.j2
new file mode 100644
index 0000000..13cd2b1
--- /dev/null
+++ b/scripts/aidl/c_wrapper/tpl/macros/c_method_fn.j2
@@ -0,0 +1,198 @@
+### ****************
+### Arg Comments
+### ****************
+
+{% macro arg_to_comment(arg) %}
+{% if arg.spec.isInput %}
+ {% if arg.spec.isArray %}
+ * @param {{arg.name}}
+ * @param {{arg.name}}_size: size of the input {{arg.name}} array,
+ * must be set to {{arg.spec.arraySize}}
+ {% elif arg.spec.isPayload %}
+ * @param {{arg.name}}
+ * @param {{arg.name}}_size: size of the input {{arg.name}} array
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ * @param {{field.name}}
+ {% endfor %}
+ {% else %}
+ * @param {{arg.name}}
+ {% endif %}
+{%- else %}
+ {% if arg.spec.isPayload %}
+ * @param[out] {{arg.name}}
+ * @param[out] {{arg.name}}_buf_size: size of the output {{arg.name}} array
+ * @param[out] {{arg.name}}_size: number of bytes written into the output array
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ * @param[out] {{field.name}}
+ {% endfor %}
+ {% else %}
+ * @param[out] {{arg.name}}
+ {% endif %}
+{%- endif %}
+{%- endmacro %}
+
+### *******************
+### Method Comments
+### *******************
+
+{% macro comment(impl_name, method) %}
+/**
+ * {{impl_name}}_{{method.name}}()
+ *
+{% for arg in method.args %}
+{{arg_to_comment(arg)}}
+{%- endfor %}
+{% if method.args|length > 0 %}
+ *
+{% endif %}
+ * @return: 0 on success, or an error code < 0 on failure.
+ */
+{%- endmacro %}
+
+### ****************
+### Arg Signature
+### ****************
+
+{% macro arg_to_c(arg) %}
+{% if arg.spec.isInput %}
+ {% if arg.spec.isArray %}
+ uint8_t* {{arg.name}},
+ size_t {{arg.name}}_size
+ {% elif arg.spec.isPayload %}
+ uint8_t* {{arg.name}},
+ size_t {{arg.name}}_size
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ {{field.qualType}} {{field.name}} {{ "," if not loop.last else "" }}
+ {% endfor %}
+ {% else %}
+ {{arg.qualType}} {{arg.name}}
+ {% endif %}
+{% else %}
+ {% if arg.spec.isPayload %}
+ uint8_t* {{arg.name}},
+ size_t {{arg.name}}_buf_size,
+ size_t* {{arg.name}}_size
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ {{field.qualType}}* {{field.name}} {{ "," if not loop.last else "" }}
+ {% endfor %}
+ {% else %}
+ {{arg.qualType}} {{arg.name}}
+ {% endif %}
+{%- endif %}
+{%- endmacro %}
+
+### *******************
+### Method Signature
+### *******************
+
+{% macro signature(impl_name, method, hdr) %}
+{% if not hdr %}
+extern "C"{{" "}}
+{%- endif %}
+{% if method.args|length == 0 %}
+int {{impl_name}}_{{method.name}}(void)
+{%- else %}
+int {{impl_name}}_{{method.name}}(
+{% for arg in method.args %}
+{{arg_to_c(arg)}}{{ "," if not loop.last else "" }}
+{%- endfor %}
+)
+{%- endif %}
+{%- endmacro %}
+
+
+### *******************************************************
+### Arg Prolog: Initialisation prior to method invocation
+### *******************************************************
+
+{% macro arg_prolog(iface_name, domain, arg) %}
+{% if arg.spec.isInput %}
+ {% if arg.spec.isArray %}
+ if ({{arg.name}}_size != {{arg.spec.arraySize}}) {
+ TLOGE("{{arg.name}} is not its expected size {{arg.spec.arraySize}}.\n");
+ return ERR_BAD_LEN;
+ }
+
+ {{arg.qualType.replace("&", "")}} {{arg.name}}_array;
+ memcpy({{arg.name}}_array.data(), {{arg.name}}, {{arg.name}}_array.size());
+
+ {% elif arg.spec.isPayload %}
+ if ({{arg.name}}_size > aidl::{{iface_name}}::MAX_PAYLOAD_REQ) {
+ TLOGE("{{arg.name}} exceeds aidl::{{iface_name}}::MAX_PAYLOAD_REQ limit.\n");
+ return ERR_BAD_LEN;
+ }
+
+ const trusty::aidl::Payload {{arg.name}}_payload{const_cast<uint8_t*>({{arg.name}}),
+ static_cast<uint32_t>({{arg.name}}_size)};
+
+ {% elif arg.xref %}
+ const {{arg.qualType.replace(" &","")}} {{arg.name}} = {
+ {% for field in arg.xref.fields %}
+ .{{field.name}} = {{field.name}}{{ "," if not loop.last else "" }}
+ {% endfor %}
+ };
+ {% endif %}
+{% else %}
+ {% if arg.spec.isPayload %}
+ if ({{arg.name}}_size > aidl::{{iface_name}}::MAX_PAYLOAD_RESP) {
+ TLOGE("{{arg.name}} exceeds aidl::{{iface_name}}::MAX_PAYLOAD_RESP limit.\n");
+ return ERR_BAD_LEN;
+ }
+
+ trusty::aidl::Payload {{arg.name}}_payload{const_cast<uint8_t*>({{arg.name}}),
+ static_cast<uint32_t>({{arg.name}}_buf_size)};
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ assert({{field.name}});
+ {% endfor %}
+ {{arg.qualType.replace(" *","")}} {{arg.name}};
+ {% endif %}
+{%- endif %}
+{%- endmacro %}
+
+
+### *******************************************************
+### Arg Epilog: Update after method invocation
+### *******************************************************
+
+{% macro arg_epilog(arg) %}
+{% if not arg.spec.isInput %}
+ {% if arg.spec.isPayload %}
+ *{{arg.name}}_size = {{arg.name}}_payload.size();
+ {% elif arg.xref %}
+ {% for field in arg.xref.fields %}
+ *{{field.name}} = {{arg.name}}.{{field.name}};
+ {% endfor %}
+ {% endif %}
+{%- endif %}
+{%- endmacro %}
+
+### ****************
+### Arg Invoke
+### ****************
+
+{% macro arg_invoke(arg) %}
+{% if arg.spec.isInput %}
+ {% if arg.spec.isArray %}
+ {{arg.name}}_array
+ {% elif arg.spec.isPayload %}
+ {{arg.name}}_payload
+ {% elif arg.xref %}
+ {{arg_name}}
+ {% else %}
+ {{arg.name}}
+ {% endif %}
+{% else %}
+ {% if arg.spec.isPayload %}
+ &{{arg.name}}_payload
+ {% elif arg.xref %}
+ &{{arg.name}}
+ {% else %}
+ {{arg.name}}
+ {% endif %}
+{%- endif %}
+{%- endmacro %}