feat(tlc): add command gen-header
Introduce the gen-header command to the tool, enabling developers to
create language bindings. Currently, it supports generating C headers
from a transfer list.
Change-Id: Ibec75639c38577802d5abe55c7bc718740aad2b8
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
diff --git a/tools/tlc/poetry.lock b/tools/tlc/poetry.lock
index 60402f1..decec59 100644
--- a/tools/tlc/poetry.lock
+++ b/tools/tlc/poetry.lock
@@ -1431,4 +1431,4 @@
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "9ad739a282f180a9c47cf8cb8f1f53a167e1739b1c5bf572932384b45749df26"
+content-hash = "aac9123f3fa544b8c3e9b085f41f5a1c6c4ed2d59ce3236dcda6ea2aef5a694c"
diff --git a/tools/tlc/pyproject.toml b/tools/tlc/pyproject.toml
index ea842e5..b606238 100644
--- a/tools/tlc/pyproject.toml
+++ b/tools/tlc/pyproject.toml
@@ -38,6 +38,7 @@
rich = "^10.14.0"
click = "^8.1.7"
pyyaml = "^6.0.1"
+tox = "^4.18.0"
jinja2 = "^3.1.4"
[tool.poetry.group.dev]
diff --git a/tools/tlc/tests/test_cli.py b/tools/tlc/tests/test_cli.py
index 4fad349..a5ef30e 100644
--- a/tools/tlc/tests/test_cli.py
+++ b/tools/tlc/tests/test_cli.py
@@ -11,6 +11,7 @@
from math import ceil, log2
from pathlib import Path
+from re import findall, search
from unittest import mock
import pytest
@@ -383,6 +384,69 @@
assert actual == expected
+@pytest.mark.parametrize("option", ["-O", "--output"])
+def test_gen_tl_header_with_output_name(tlcrunner, tmptlstr, option, filename="test.h"):
+ with tlcrunner.isolated_filesystem():
+ result = tlcrunner.invoke(
+ cli,
+ [
+ "gen-header",
+ option,
+ filename,
+ tmptlstr,
+ ],
+ )
+
+ assert result.exit_code == 0
+ assert Path(filename).exists()
+
+
+def test_gen_tl_with_fdt_header(tmptlstr, tmpfdt):
+ tlcrunner = CliRunner()
+
+ with tlcrunner.isolated_filesystem():
+ tlcrunner.invoke(cli, ["create", "--size", 1000, "--fdt", tmpfdt, tmptlstr])
+
+ result = tlcrunner.invoke(
+ cli,
+ [
+ "gen-header",
+ tmptlstr,
+ ],
+ )
+
+ assert result.exit_code == 0
+ assert Path("header.h").exists()
+
+ with open("header.h", "r") as f:
+ dtb_match = search(r"DTB_OFFSET\s+(\d+)", "".join(f.readlines()))
+ assert dtb_match and dtb_match[1].isnumeric()
+
+
+def test_gen_empty_tl_c_header(tlcrunner, tmptlstr):
+ with tlcrunner.isolated_filesystem():
+ result = tlcrunner.invoke(
+ cli,
+ [
+ "gen-header",
+ tmptlstr,
+ ],
+ )
+
+ assert result.exit_code == 0
+ assert Path("header.h").exists()
+
+ with open("header.h", "r") as f:
+ lines = "".join(f.readlines())
+
+ assert TransferList.hdr_size == int(
+ findall(r"SIZE\s+(0x[0-9a-fA-F]+|\d+)", lines)[0], 16
+ )
+ assert TransferList.version == int(
+ findall(r"VERSION.+(0x[0-9a-fA-F]+|\d+)", lines)[0]
+ )
+
+
def bytes_to_hex(data: bytes) -> str:
"""Convert bytes to a hex string in the same format as the debugger in
ArmDS
diff --git a/tools/tlc/tlc/cli.py b/tools/tlc/tlc/cli.py
index 1d4949d..3d60938 100644
--- a/tools/tlc/tlc/cli.py
+++ b/tools/tlc/tlc/cli.py
@@ -12,6 +12,7 @@
from pathlib import Path
import click
+import jinja2
import yaml
from tlc.tl import *
@@ -166,6 +167,34 @@
@cli.command()
@click.argument("filename", type=click.Path(exists=True, dir_okay=False))
+@click.option(
+ "--output",
+ "-O",
+ type=click.Path(exists=False),
+ help="Output filename for the header",
+ default=Path("header.h"),
+)
+def gen_header(filename, output):
+ """Generate a header with common definitions."""
+ tl = TransferList.fromfile(filename)
+ tmp_keys = tl.__dict__
+ tmp_keys["header_guard"] = Path(output).name.replace(".", "_").upper()
+
+ dtb_te = tl.get_entry(1)
+
+ if dtb_te:
+ tmp_keys["dtb_offset"] = dtb_te.offset + dtb_te.hdr_size
+
+ env = jinja2.Environment(
+ loader=jinja2.PackageLoader("tlc", "templates"),
+ )
+ template = env.get_template("header.h.j2")
+ with open(output, "w") as f:
+ f.write(template.render(tmp_keys))
+
+
+@cli.command()
+@click.argument("filename", type=click.Path(exists=True, dir_okay=False))
def validate(filename):
"""Validate the contents of an existing Transfer List."""
TransferList.fromfile(filename)
diff --git a/tools/tlc/tlc/templates/header.h.j2 b/tools/tlc/tlc/templates/header.h.j2
new file mode 100644
index 0000000..87707ce
--- /dev/null
+++ b/tools/tlc/tlc/templates/header.h.j2
@@ -0,0 +1,16 @@
+/*
+ * Auto-generated by TLC, this file includes declarations and macros
+ * derived from a Transfer List input.
+ */
+
+#ifndef {{ header_guard }}
+#define {{ header_guard }}
+
+{% if dtb_offset -%}
+#define TRANSFER_LIST_DTB_OFFSET {{ "0x%x" % dtb_offset }}
+{%- endif %}
+#define TRANSFER_LIST_CONVENTION_VERSION {{ version }}
+#define TRANSFER_LIST_HEADER_SIZE {{ "0x%x" % hdr_size }}
+#define TRANSFER_LIST_SIZE {{ "0x%x" % size }}
+
+#endif /* {{ header_guard }} */
diff --git a/tools/tlc/tlc/tl.py b/tools/tlc/tlc/tl.py
index b8bb399..98d2205 100644
--- a/tools/tlc/tlc/tl.py
+++ b/tools/tlc/tlc/tl.py
@@ -8,7 +8,7 @@
"""Module containing definitions pertaining to the 'Transfer List' (TL) type."""
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
import math
import struct
@@ -93,7 +93,7 @@
self.size = self.hdr_size
self.total_size = max_size
self.flags = flags
- self.entries: List["TransferEntry"] = []
+ self.entries: List[TransferEntry] = []
self.update_checksum()
def __str__(self) -> str:
@@ -197,15 +197,23 @@
sum(self.header_to_bytes()) + sum(te.sum_of_bytes for te in self.entries)
) % 256
- def get_entry_data_offset(self, tag_id: int) -> int:
- """Returns offset of data of a TE from the base of the TL."""
+ def get_entry(self, tag_id: int) -> Optional[TransferEntry]:
for te in self.entries:
if te.id == tag_id:
- return te.offset + te.hdr_size
+ return te
+
+ return None
+
+ def get_entry_data_offset(self, tag_id: int) -> int:
+ """Returns offset of data of a TE from the base of the TL."""
+ te = self.get_entry(tag_id)
+
+ if not te:
+ raise ValueError(f"Tag {tag_id} not found in TL!")
- raise ValueError(f"Tag {tag_id} not found in TL!")
+ return te.offset + te.hdr_size
- def add_transfer_entry(self, tag_id: int, data: bytes) -> "TransferEntry":
+ def add_transfer_entry(self, tag_id: int, data: bytes) -> TransferEntry:
"""Appends a TransferEntry into the internal list of TE's."""
if not (self.total_size >= self.size + TransferEntry.hdr_size + len(data)):
raise MemoryError(
@@ -220,14 +228,14 @@
def add_transfer_entry_from_struct_format(
self, tag_id: int, struct_format: str, *args: Any
- ) -> "TransferEntry":
+ ) -> TransferEntry:
struct_format = "<" + struct_format
data = struct.pack(struct_format, *args)
return self.add_transfer_entry(tag_id, data)
def add_entry_point_info_transfer_entry(
self, entry: Dict[str, Any]
- ) -> "TransferEntry":
+ ) -> TransferEntry:
"""Add entry_point_info transfer entry
:param entry: Dictionary of the transfer entry, in the same format as
@@ -285,7 +293,7 @@
def add_transfer_entry_from_dict(
self,
entry: Dict[str, Any],
- ) -> "TransferEntry":
+ ) -> TransferEntry:
"""Add a transfer entry from data in a dictionary
The dictionary should have the same format as the entries in the yaml
@@ -320,7 +328,7 @@
else:
raise ValueError(f"Invalid transfer entry {entry}.")
- def add_transfer_entry_from_file(self, tag_id: int, path: Path) -> "TransferEntry":
+ def add_transfer_entry_from_file(self, tag_id: int, path: Path) -> TransferEntry:
with open(path, "rb") as f:
return self.add_transfer_entry(tag_id, f.read())