diff --git a/poetry.lock b/poetry.lock
index cd2a41aa9d2bb8d9609a9178c1aacb71ca7fc891..9a907044a659d3995f9ab757b934e432f0e7361a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -44,13 +44,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
 
 [[package]]
 name = "build"
-version = "1.2.1"
+version = "1.2.2"
 description = "A simple, correct Python build frontend"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"},
-    {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"},
+    {file = "build-1.2.2-py3-none-any.whl", hash = "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613"},
+    {file = "build-1.2.2.tar.gz", hash = "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c"},
 ]
 
 [package.dependencies]
@@ -80,13 +80,13 @@ files = [
 
 [[package]]
 name = "certifi"
-version = "2024.7.4"
+version = "2024.8.30"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
-    {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
+    {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
+    {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
 ]
 
 [[package]]
@@ -282,19 +282,19 @@ files = [
 
 [[package]]
 name = "filelock"
-version = "3.15.4"
+version = "3.16.0"
 description = "A platform independent file lock."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
-    {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
+    {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"},
+    {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
-typing = ["typing-extensions (>=4.8)"]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"]
+typing = ["typing-extensions (>=4.12.2)"]
 
 [[package]]
 name = "idna"
@@ -610,29 +610,29 @@ testing = ["flit-core (>=2,<4)", "poetry-core (>=1.0.0)", "pytest (>=7.2.0)", "p
 
 [[package]]
 name = "platformdirs"
-version = "4.2.2"
+version = "4.3.2"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
-    {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
+    {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"},
+    {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
-type = ["mypy (>=1.8)"]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.11.2)"]
 
 [[package]]
 name = "plotly"
-version = "5.23.0"
+version = "5.24.0"
 description = "An open-source, interactive data visualization library for Python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "plotly-5.23.0-py3-none-any.whl", hash = "sha256:76cbe78f75eddc10c56f5a4ee3e7ccaade7c0a57465546f02098c0caed6c2d1a"},
-    {file = "plotly-5.23.0.tar.gz", hash = "sha256:89e57d003a116303a34de6700862391367dd564222ab71f8531df70279fc0193"},
+    {file = "plotly-5.24.0-py3-none-any.whl", hash = "sha256:0e54efe52c8cef899f7daa41be9ed97dfb6be622613a2a8f56a86a0634b2b67e"},
+    {file = "plotly-5.24.0.tar.gz", hash = "sha256:eae9f4f54448682442c92c1e97148e3ad0c52f0cf86306e1b76daba24add554a"},
 ]
 
 [package.dependencies]
@@ -869,13 +869,13 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
 
 [[package]]
 name = "setuptools"
-version = "74.0.0"
+version = "74.1.2"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "setuptools-74.0.0-py3-none-any.whl", hash = "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f"},
-    {file = "setuptools-74.0.0.tar.gz", hash = "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e"},
+    {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"},
+    {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"},
 ]
 
 [package.extras]
@@ -1147,6 +1147,7 @@ develop = true
 
 [package.dependencies]
 click = "^8.1.7"
+jinja2 = "^3.1.4"
 pyyaml = "^6.0.1"
 rich = "^10.14.0"
 tox = "^4.18.0"
@@ -1169,17 +1170,17 @@ files = [
 
 [[package]]
 name = "tox"
-version = "4.18.0"
+version = "4.18.1"
 description = "tox is a generic virtualenv management and test command line tool"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "tox-4.18.0-py3-none-any.whl", hash = "sha256:0a457400cf70615dc0627eb70d293e80cd95d8ce174bb40ac011011f0c03a249"},
-    {file = "tox-4.18.0.tar.gz", hash = "sha256:5dfa1cab9f146becd6e351333a82f9e0ade374451630ba65ee54584624c27b58"},
+    {file = "tox-4.18.1-py3-none-any.whl", hash = "sha256:35d472032ee1f73fe20c3e0e73d7073a4e85075c86ff02c576f9fc7c6a15a578"},
+    {file = "tox-4.18.1.tar.gz", hash = "sha256:3c0c96bc3a568a5c7e66387a4cfcf8c875b52e09f4d47c9f7a277ec82f1a0b11"},
 ]
 
 [package.dependencies]
-cachetools = ">=5.4"
+cachetools = ">=5.5"
 chardet = ">=5.2"
 colorama = ">=0.4.6"
 filelock = ">=3.15.4"
@@ -1191,8 +1192,8 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
 virtualenv = ">=20.26.3"
 
 [package.extras]
-docs = ["furo (>=2024.7.18)", "sphinx (>=7.4.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.3)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"]
-testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.3)", "time-machine (>=2.14.2)", "wheel (>=0.43)"]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-argparse-cli (>=1.17)", "sphinx-autodoc-typehints (>=2.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=24.8)"]
+testing = ["build[virtualenv] (>=1.2.2)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=74.1.2)", "time-machine (>=2.15)", "wheel (>=0.44)"]
 
 [[package]]
 name = "typer"
@@ -1246,13 +1247,13 @@ zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.26.3"
+version = "20.26.4"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
-    {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
+    {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"},
+    {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"},
 ]
 
 [package.dependencies]
diff --git a/tools/tlc/poetry.lock b/tools/tlc/poetry.lock
index 60402f155a627221f1c478145ae8c6bd30dbe649..decec59878e5f45fdd4294c78e568f35201a64d1 100644
--- a/tools/tlc/poetry.lock
+++ b/tools/tlc/poetry.lock
@@ -1431,4 +1431,4 @@ files = [
 [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 ea842e53bc224c4619d89449fd026964c595ed7e..b606238411919e4b8bb5b4bf3a0a3876e03425e7 100644
--- a/tools/tlc/pyproject.toml
+++ b/tools/tlc/pyproject.toml
@@ -38,6 +38,7 @@ typer = {extras = ["all"], version = "^0.4.0"}
 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 4fad349eb68fe36bf203c99acbc81e76d308babb..a5ef30eed6fcfd2746cdba444566338d70357b7d 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 @@ def test_create_from_yaml_check_exact_data(
     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 1d4949d6791675e93a84806f5fd37c8b56f87e15..3d609380d87e4dd63a940ac22486b98f81c2fe8d 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 *
@@ -164,6 +165,34 @@ def unpack(filename, c):
             f.write(te.data)
 
 
+@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):
diff --git a/tools/tlc/tlc/templates/header.h.j2 b/tools/tlc/tlc/templates/header.h.j2
new file mode 100644
index 0000000000000000000000000000000000000000..87707cec2833f442e648995e539de9e49f183903
--- /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 b8bb3992001acac8450de4ddde2f3f7d7c27d587..98d2205bf4a30a21f39e8c7a498dd79b9414976a 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 @@ class TransferList:
         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 @@ class TransferList:
             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 @@ class TransferList:
 
     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 @@ class TransferList:
     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 @@ class TransferList:
         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())