Merge changes from topic "hm/handoff" into integration
* changes:
fix(arm): move HW_CONFIG relocation into BL31
feat: add option to input attr as string of flag names
feat: add option to input text instead of tag id number
feat: add creating transfer lists from yaml files
diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst
index c8ef7ac..fa660dc 100644
--- a/docs/tools/transfer-list-compiler.rst
+++ b/docs/tools/transfer-list-compiler.rst
@@ -59,6 +59,12 @@
provided tag ID. It only checks that the tags provided as input are within
range and that there is sufficient memory to include their TE's.
+You can also create a TL from a YAML config file.
+
+.. code ::
+
+ tlc create --from-yaml config.yaml tl.bin
+
Printing the contents of a TL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -186,9 +192,120 @@
#. Ensures that the specified version is greater than or equal to the tool’s current version.
#. Verifies alignment criteria for all TE’s.
+YAML Config File Format
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Example YAML config file:
+
+.. code::
+
+ execution_state: aarch32
+ has_checksum: true
+ max_size: 4096
+ entries:
+ - tag_id: 258 # entry point info
+ ep_info:
+ args:
+ - 67112968
+ - 67112960
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ h:
+ attr: 8
+ type: 1
+ version: 2
+ pc: 67239936
+ spsr: 467
+ - tag_id: 3 # memory layout
+ addr: 8
+ size: 8
+ - tag_id: 1, # fdt
+ blob_file_path: "fdt.bin",
+
+`max_size` defaults to `0x1000`, `execution_state` defaults to `aarch64`, and `has_checksum`
+defaults to `true`.
+
+The fields of the YAML file should match the fields in the specification for the transfer list. You
+don't need to give the hdr_size or data_size fields. For example, a memory layout entry would have
+an entry like:
+
+.. code::
+
+ tag_id: 3
+ addr: 8
+ size: 8
+
+You can input blob files by giving paths to the current working directory. You can do this for any
+TE type. For example, an FDT layout would have an entry like:
+
+.. code::
+
+ tag_id: 1,
+ blob_file_path: "fdt.bin",
+
+You can input C-types by giving its fields. For example, an entry point
+info entry would have an entry like:
+
+.. code::
+
+ tag_id: 258
+ ep_info:
+ args:
+ - 67112968
+ - 67112960
+ - 0
+ - 0
+ h:
+ attr: 8
+ type: 1
+ version: 2
+ lr_svc: 0
+ pc: 67239936
+ spsr: 467
+
+You can give the name of the tag instead of the tag id number. The valid tag names are in the
+`transfer_entry_formats` dict in `tools/tlc/tlc/tl.py`_. Some examples are:
+
+* empty
+* fdt
+* hob_block
+* hob_list
+
+You can input the attr field of entry_point_info as a string of flag
+names separated by `|`. The names are taken from ep_info_exp.h in TF-A.
+For example:
+
+.. code::
+
+ has_checksum: true
+ max_size: 4096
+ entries:
+ - tag_id: 0x102
+ ep_info:
+ args:
+ - 67112976
+ - 67112960
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ h:
+ attr: EP_NON_SECURE | EP_ST_ENABLE
+ type: 1
+ version: 2
+ pc: 67239936
+ spsr: 965
+
--------------
*Copyright (c) 2024, Arm Limited. All rights reserved.*
.. _Firmware Handoff specification: https://github.com/FirmwareHandoff/firmware_handoff/
.. _tools/tlc/pyproject.toml: https://review.trustedfirmware.org/plugins/gitiles/TF-A/trusted-firmware-a/+/refs/heads/master/tools/tlc/pyproject.toml
+.. _tools/tlc/tlc/tl.py: https://review.trustedfirmware.org/plugins/gitiles/TF-A/trusted-firmware-a/+/refs/heads/master/tools/tlc/tlc/tl.py
diff --git a/include/plat/arm/common/plat_arm.h b/include/plat/arm/common/plat_arm.h
index ba8df2a..3ea29eb 100644
--- a/include/plat/arm/common/plat_arm.h
+++ b/include/plat/arm/common/plat_arm.h
@@ -284,10 +284,7 @@
/* Firmware Handoff utility functions */
void arm_transfer_list_dyn_cfg_init(struct transfer_list_header *secure_tl);
void arm_transfer_list_populate_ep_info(bl_mem_params_node_t *next_param_node,
- struct transfer_list_header *secure_tl,
- struct transfer_list_header *ns_tl);
-void arm_transfer_list_copy_hw_config(struct transfer_list_header *secure_tl,
- struct transfer_list_header *ns_tl);
+ struct transfer_list_header *secure_tl);
/* TSP utility functions */
void arm_tsp_early_platform_setup(void);
diff --git a/plat/arm/common/arm_bl2_setup.c b/plat/arm/common/arm_bl2_setup.c
index 99243dc..b5a7db1 100644
--- a/plat/arm/common/arm_bl2_setup.c
+++ b/plat/arm/common/arm_bl2_setup.c
@@ -162,16 +162,6 @@
#if defined(PLAT_ARM_MEM_PROT_ADDR)
arm_nor_psci_do_static_mem_protect();
#endif
-
-#if TRANSFER_LIST
- ns_tl = transfer_list_init((void *)FW_NS_HANDOFF_BASE,
- PLAT_ARM_FW_HANDOFF_SIZE);
-
- if (ns_tl == NULL) {
- ERROR("Non-secure transfer list initialisation failed!");
- panic();
- }
-#endif
}
void bl2_platform_setup(void)
@@ -326,7 +316,8 @@
#if TRANSFER_LIST
if (image_id == HW_CONFIG_ID) {
- arm_transfer_list_copy_hw_config(secure_tl, ns_tl);
+ /* Refresh the now stale checksum following loading of HW_CONFIG into the TL. */
+ transfer_list_update_checksum(secure_tl);
}
#endif /* TRANSFER_LIST */
@@ -340,5 +331,5 @@
&next_param_node->ep_info);
assert(ep != NULL);
- arm_transfer_list_populate_ep_info(next_param_node, secure_tl, ns_tl);
+ arm_transfer_list_populate_ep_info(next_param_node, secure_tl);
}
diff --git a/plat/arm/common/arm_bl31_setup.c b/plat/arm/common/arm_bl31_setup.c
index c7dbb7a..e91746b 100644
--- a/plat/arm/common/arm_bl31_setup.c
+++ b/plat/arm/common/arm_bl31_setup.c
@@ -25,6 +25,8 @@
#include <platform_def.h>
static struct transfer_list_header *secure_tl __unused;
+static struct transfer_list_header *ns_tl __unused;
+
/*
* Placeholder variables for copying the arguments that have been passed to
* BL31 from BL2.
@@ -95,7 +97,12 @@
assert(sec_state_is_valid(type));
if (type == NON_SECURE) {
+#if TRANSFER_LIST && !RESET_TO_BL31
+ next_image_info = transfer_list_set_handoff_args(
+ ns_tl, &bl33_image_ep_info);
+#else
next_image_info = &bl33_image_ep_info;
+#endif
}
#if ENABLE_RME
else if (type == REALM) {
@@ -357,6 +364,28 @@
******************************************************************************/
void arm_bl31_platform_setup(void)
{
+ struct transfer_list_entry *te __unused;
+
+#if TRANSFER_LIST && !RESET_TO_BL31
+ /* Initialise the non-secure world tl, BL31 may modify the HW_CONFIG so defer
+ * copying it until later.
+ */
+ ns_tl = transfer_list_init((void *)FW_NS_HANDOFF_BASE,
+ PLAT_ARM_FW_HANDOFF_SIZE);
+
+ if (ns_tl == NULL) {
+ ERROR("Non-secure transfer list initialisation failed!");
+ panic();
+ }
+
+#if !RESET_TO_BL2
+ te = transfer_list_find(secure_tl, TL_TAG_FDT);
+ assert(te != NULL);
+
+ fconf_populate("HW_CONFIG", (uintptr_t)transfer_list_entry_data(te));
+#endif /* !(RESET_TO_BL2 && RESET_TO_BL31) */
+#endif /* TRANSFER_LIST */
+
/* Initialize the GIC driver, cpu and distributor interfaces */
plat_arm_gic_driver_init();
plat_arm_gic_init();
@@ -399,9 +428,26 @@
******************************************************************************/
void arm_bl31_plat_runtime_setup(void)
{
+ struct transfer_list_entry *te __unused;
/* Initialize the runtime console */
arm_console_runtime_init();
+#if TRANSFER_LIST && !RESET_TO_BL31
+ te = transfer_list_find(secure_tl, TL_TAG_FDT);
+ assert(te != NULL);
+
+ te = transfer_list_add(ns_tl, TL_TAG_FDT, te->data_size,
+ transfer_list_entry_data(te));
+ assert(te != NULL);
+
+ /*
+ * We assume BL31 has added all TE's required by BL33 at this stage, ensure
+ * that data is visible to all observers by performing a flush operation, so
+ * they can access the updated data even if caching is not enabled.
+ */
+ flush_dcache_range((uintptr_t)ns_tl, ns_tl->size);
+#endif /* TRANSFER_LIST && !(RESET_TO_BL2 || RESET_TO_BL31) */
+
#if RECLAIM_INIT_CODE
arm_free_init_memory();
#endif
@@ -516,15 +562,5 @@
void __init bl31_plat_arch_setup(void)
{
- struct transfer_list_entry *te __unused;
-
arm_bl31_plat_arch_setup();
-
-#if TRANSFER_LIST && !(RESET_TO_BL2 || RESET_TO_BL31)
- te = transfer_list_find(secure_tl, TL_TAG_FDT);
- assert(te != NULL);
-
- /* Populate HW_CONFIG device tree with the mapped address */
- fconf_populate("HW_CONFIG", (uintptr_t)transfer_list_entry_data(te));
-#endif
}
diff --git a/plat/arm/common/arm_transfer_list.c b/plat/arm/common/arm_transfer_list.c
index d144bbb..59fb039 100644
--- a/plat/arm/common/arm_transfer_list.c
+++ b/plat/arm/common/arm_transfer_list.c
@@ -30,8 +30,7 @@
}
void arm_transfer_list_populate_ep_info(bl_mem_params_node_t *next_param_node,
- struct transfer_list_header *secure_tl,
- struct transfer_list_header *ns_tl)
+ struct transfer_list_header *secure_tl)
{
uint32_t next_exe_img_id;
entry_point_info_t *ep;
@@ -53,10 +52,7 @@
ep = transfer_list_entry_data(te);
- if (next_exe_img_id == BL33_IMAGE_ID) {
- ep = transfer_list_set_handoff_args(ns_tl, ep);
- assert(ep != NULL);
- } else if ((next_exe_img_id == BL32_IMAGE_ID) && SPMC_AT_EL3) {
+ if ((next_exe_img_id == BL32_IMAGE_ID) && SPMC_AT_EL3) {
/*
* Populate the BL32 image base, size and max limit in
* the entry point information, since there is no
@@ -78,19 +74,3 @@
flush_dcache_range((uintptr_t)secure_tl, secure_tl->size);
}
-
-void arm_transfer_list_copy_hw_config(struct transfer_list_header *secure_tl,
- struct transfer_list_header *ns_tl)
-{
- struct transfer_list_entry *te =
- transfer_list_find(secure_tl, TL_TAG_FDT);
- assert(te != NULL);
-
- /* Refresh the now stale checksum following loading of HW_CONFIG into the TL. */
- transfer_list_update_checksum(secure_tl);
-
- /* Copy the hardware configuration to the non-secure TL. */
- te = transfer_list_add(ns_tl, TL_TAG_FDT, te->data_size,
- transfer_list_entry_data(te));
- assert(te != NULL);
-}
diff --git a/poetry.lock b/poetry.lock
index d72e14a..b465f48 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -893,6 +893,7 @@
[package.dependencies]
click = "^8.1.7"
+pyyaml = "^6.0.1"
rich = "^10.14.0"
typer = {version = "^0.4.0", extras = ["all"]}
diff --git a/tools/tlc/assets/images/coverage.svg b/tools/tlc/assets/images/coverage.svg
index 3438732..b6c4e36 100644
--- a/tools/tlc/assets/images/coverage.svg
+++ b/tools/tlc/assets/images/coverage.svg
@@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="31.5" y="14">coverage</text>
- <text x="80" y="15" fill="#010101" fill-opacity=".3">97%</text>
- <text x="80" y="14">97%</text>
+ <text x="80" y="15" fill="#010101" fill-opacity=".3">95%</text>
+ <text x="80" y="14">95%</text>
</g>
</svg>
diff --git a/tools/tlc/poetry.lock b/tools/tlc/poetry.lock
index 5a39322..839f236 100644
--- a/tools/tlc/poetry.lock
+++ b/tools/tlc/poetry.lock
@@ -386,13 +386,13 @@
[[package]]
name = "exceptiongroup"
-version = "1.2.1"
+version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
- {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
- {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
@@ -1107,18 +1107,19 @@
[[package]]
name = "setuptools"
-version = "70.2.0"
+version = "72.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"},
- {file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"},
+ {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"},
+ {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"},
]
[package.extras]
+core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "shellingham"
@@ -1191,13 +1192,13 @@
[[package]]
name = "tomlkit"
-version = "0.12.5"
+version = "0.13.0"
description = "Style preserving TOML library"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"},
- {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"},
+ {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"},
+ {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"},
]
[[package]]
@@ -1352,4 +1353,4 @@
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "60bdb4a8b67815f01b4e7089d9f7664afcb9041fa8adf5aa92d977f4e2d5b4b2"
+content-hash = "cfcb196cda412f6139302937640455aa8154d7979c69017fe45ddd528e4a1bf2"
diff --git a/tools/tlc/pyproject.toml b/tools/tlc/pyproject.toml
index 30875a5..5661abf 100644
--- a/tools/tlc/pyproject.toml
+++ b/tools/tlc/pyproject.toml
@@ -37,6 +37,7 @@
typer = {extras = ["all"], version = "^0.4.0"}
rich = "^10.14.0"
click = "^8.1.7"
+pyyaml = "^6.0.1"
[tool.poetry.dev-dependencies]
bandit = "^1.7.1"
diff --git a/tools/tlc/tests/conftest.py b/tools/tlc/tests/conftest.py
index 6b28e43..b8f88b5 100644
--- a/tools/tlc/tests/conftest.py
+++ b/tools/tlc/tests/conftest.py
@@ -10,6 +10,7 @@
""" Common configurations and fixtures for test environment."""
import pytest
+import yaml
from click.testing import CliRunner
from tlc.cli import cli
@@ -21,12 +22,43 @@
@pytest.fixture
+def tmpyamlconfig(tmpdir):
+ return tmpdir.join("config.yaml").strpath
+
+
+@pytest.fixture
def tmpfdt(tmpdir):
fdt = tmpdir.join("fdt.dtb")
fdt.write_binary(b"\x00" * 100)
return fdt
+@pytest.fixture(params=[1, 2, 3, 4, 5, 0x100, 0x101, 0x102, 0x104])
+def non_empty_tag_id(request):
+ return request.param
+
+
+@pytest.fixture
+def tmpyamlconfig_blob_file(tmpdir, tmpfdt, non_empty_tag_id):
+ config_path = tmpdir.join("config.yaml")
+
+ config = {
+ "has_checksum": True,
+ "max_size": 0x1000,
+ "entries": [
+ {
+ "tag_id": non_empty_tag_id,
+ "blob_file_path": tmpfdt.strpath,
+ },
+ ],
+ }
+
+ with open(config_path, "w") as f:
+ yaml.safe_dump(config, f)
+
+ return config_path
+
+
@pytest.fixture
def tlcrunner(tmptlstr):
runner = CliRunner()
diff --git a/tools/tlc/tests/test_cli.py b/tools/tlc/tests/test_cli.py
index d79773b..99b5816 100644
--- a/tools/tlc/tests/test_cli.py
+++ b/tools/tlc/tests/test_cli.py
@@ -11,8 +11,11 @@
from pathlib import Path
from unittest import mock
+from math import log2, ceil
import pytest
+import pytest
+import yaml
from click.testing import CliRunner
from tlc.cli import cli
@@ -203,3 +206,208 @@
assert result.exit_code == 0
else:
assert result.exit_code == 1
+
+
+def test_create_entry_from_yaml_and_blob_file(
+ tlcrunner, tmpyamlconfig_blob_file, tmptlstr, non_empty_tag_id
+):
+ tlcrunner.invoke(
+ cli,
+ [
+ "create",
+ "--from-yaml",
+ tmpyamlconfig_blob_file.strpath,
+ tmptlstr,
+ ],
+ )
+
+ tl = TransferList.fromfile(tmptlstr)
+ assert tl is not None
+ assert len(tl.entries) == 1
+ assert tl.entries[0].id == non_empty_tag_id
+
+
+@pytest.mark.parametrize(
+ "entry",
+ [
+ {"tag_id": 0},
+ {
+ "tag_id": 0x104,
+ "addr": 0x0400100000000010,
+ "size": 0x0003300000000000,
+ },
+ {
+ "tag_id": 0x100,
+ "pp_addr": 100,
+ },
+ {
+ "tag_id": "optee_pageable_part",
+ "pp_addr": 100,
+ },
+ ],
+)
+def test_create_from_yaml_check_sum_bytes(tlcrunner, tmpyamlconfig, tmptlstr, entry):
+ """Test creating a TL from a yaml file, but only check that the sum of the
+ data in the yaml file matches the sum of the data in the TL. This means
+ you don't have to type the exact sequence of expected bytes. All the data
+ in the yaml file must be integers (except for the tag IDs, which can be
+ strings).
+ """
+ # create yaml config file
+ config = {
+ "has_checksum": True,
+ "max_size": 0x1000,
+ "entries": [entry],
+ }
+ with open(tmpyamlconfig, "w") as f:
+ yaml.safe_dump(config, f)
+
+ # invoke TLC
+ tlcrunner.invoke(
+ cli,
+ [
+ "create",
+ "--from-yaml",
+ tmpyamlconfig,
+ tmptlstr,
+ ],
+ )
+
+ # open created TL, and check
+ tl = TransferList.fromfile(tmptlstr)
+ assert tl is not None
+ assert len(tl.entries) == 1
+
+ # Check that the sum of all the data in the transfer entry in the yaml file
+ # is the same as the sum of all the data in the transfer list. Don't count
+ # the tag id or the TE headers.
+
+ # every item in the entry dict must be an integer
+ yaml_total = 0
+ for key, data in iter_nested_dict(entry):
+ if key != "tag_id":
+ num_bytes = ceil(log2(data + 1) / 8)
+ yaml_total += sum(data.to_bytes(num_bytes, "little"))
+
+ tl_total = sum(tl.entries[0].data)
+
+ assert tl_total == yaml_total
+
+
+@pytest.mark.parametrize(
+ "entry,expected",
+ [
+ (
+ {
+ "tag_id": 0x102,
+ "ep_info": {
+ "h": {
+ "type": 0x01,
+ "version": 0x02,
+ "attr": 8,
+ },
+ "pc": 67239936,
+ "spsr": 965,
+ "args": [67112976, 67112960, 0, 0, 0, 0, 0, 0],
+ },
+ },
+ (
+ "0x00580201 0x00000008 0x04020000 0x00000000 "
+ "0x000003C5 0x00000000 0x04001010 0x00000000 "
+ "0x04001000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000"
+ ),
+ ),
+ (
+ {
+ "tag_id": 0x102,
+ "ep_info": {
+ "h": {
+ "type": 0x01,
+ "version": 0x02,
+ "attr": "EP_NON_SECURE | EP_ST_ENABLE",
+ },
+ "pc": 67239936,
+ "spsr": 965,
+ "args": [67112976, 67112960, 0, 0, 0, 0, 0, 0],
+ },
+ },
+ (
+ "0x00580201 0x00000005 0x04020000 0x00000000 "
+ "0x000003C5 0x00000000 0x04001010 0x00000000 "
+ "0x04001000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000 0x00000000 0x00000000 "
+ "0x00000000 0x00000000"
+ ),
+ ),
+ ],
+)
+def test_create_from_yaml_check_exact_data(
+ tlcrunner, tmpyamlconfig, tmptlstr, entry, expected
+):
+ """Test creating a TL from a yaml file, checking the exact sequence of
+ bytes. This is useful for checking that the alignment is correct. You can
+ get the expected sequence of bytes by copying it from the ArmDS debugger.
+ """
+ # create yaml config file
+ config = {
+ "has_checksum": True,
+ "max_size": 0x1000,
+ "entries": [entry],
+ }
+ with open(tmpyamlconfig, "w") as f:
+ yaml.safe_dump(config, f)
+
+ # invoke TLC
+ tlcrunner.invoke(
+ cli,
+ [
+ "create",
+ "--from-yaml",
+ tmpyamlconfig,
+ tmptlstr,
+ ],
+ )
+
+ # open TL and check
+ tl = TransferList.fromfile(tmptlstr)
+ assert tl is not None
+ assert len(tl.entries) == 1
+
+ # check expected and actual data
+ actual = tl.entries[0].data
+ actual = bytes_to_hex(actual)
+
+ assert actual == expected
+
+
+def bytes_to_hex(data: bytes) -> str:
+ """Convert bytes to a hex string in the same format as the debugger in
+ ArmDS
+
+ You can copy data from the debugger in Arm Development Studio and put it
+ into a unit test. You can then run this function on the output from tlc,
+ and compare it to the data you copied.
+
+ The format is groups of 4 bytes with 0x prefixes separated by spaces.
+ Little endian is used.
+ """
+ words_hex = []
+ for i in range(0, len(data), 4):
+ word = data[i : i + 4]
+ word_int = int.from_bytes(word, "little")
+ word_hex = "0x" + f"{word_int:0>8x}".upper()
+ words_hex.append(word_hex)
+
+ return " ".join(words_hex)
+
+
+def iter_nested_dict(dictionary: dict):
+ for key, value in dictionary.items():
+ if isinstance(value, dict):
+ yield from iter_nested_dict(value)
+ else:
+ yield key, value
diff --git a/tools/tlc/tlc/cli.py b/tools/tlc/tlc/cli.py
index e574e59..1d4949d 100644
--- a/tools/tlc/tlc/cli.py
+++ b/tools/tlc/tlc/cli.py
@@ -12,6 +12,7 @@
from pathlib import Path
import click
+import yaml
from tlc.tl import *
@@ -44,15 +45,26 @@
show_default=True,
help="Settings for the TL's properties.",
)
-def create(filename, size, fdt, entry, flags):
+@click.option(
+ "--from-yaml",
+ type=click.Path(exists=True),
+ help="Create the transfer list from a YAML config file.",
+)
+def create(filename, size, fdt, entry, flags, from_yaml):
"""Create a new Transfer List."""
- tl = TransferList(size)
+ try:
+ if from_yaml:
+ with open(from_yaml, "r") as f:
+ config = yaml.safe_load(f)
- entry = (*entry, (1, fdt)) if fdt else entry
+ tl = TransferList.from_dict(config)
+ else:
+ tl = TransferList(size)
- try:
- for id, path in entry:
- tl.add_transfer_entry_from_file(id, path)
+ entry = (*entry, (1, fdt)) if fdt else entry
+
+ for id, path in entry:
+ tl.add_transfer_entry_from_file(id, path)
except MemoryError as mem_excp:
raise MemoryError(
"TL max size exceeded, consider increasing with the option -s"
diff --git a/tools/tlc/tlc/tl.py b/tools/tlc/tlc/tl.py
index a409651..3f0065d 100644
--- a/tools/tlc/tlc/tl.py
+++ b/tools/tlc/tlc/tl.py
@@ -13,12 +13,67 @@
import math
import struct
from dataclasses import dataclass
+from functools import reduce
from pathlib import Path
from tlc.te import TransferEntry
TRANSFER_LIST_ENABLE_CHECKSUM = 0b1
+# Description of each TE type. For each TE, there is a tag ID, a format (to be
+# used in struct.pack to encode the TE), and a list of field names that can
+# appear in the yaml file for that TE. Some fields are missing, if that TE has
+# to be processed differently, or if it can only be added with a blob file.
+transfer_entry_formats = {
+ 0: {
+ "tag_name": "empty",
+ "format": "4x",
+ "fields": [],
+ },
+ 1: {
+ "tag_name": "fdt",
+ },
+ 2: {
+ "tag_name": "hob_block",
+ },
+ 3: {
+ "tag_name": "hob_list",
+ },
+ 4: {
+ "tag_name": "acpi_table_aggregate",
+ },
+ 5: {
+ "tag_name": "tpm_event_log_table",
+ "fields": ["event_log", "flags"],
+ },
+ 6: {
+ "tag_name": "tpm_crb_base_address_table",
+ "format": "QI",
+ "fields": ["crb_base_address", "crb_size"],
+ },
+ 0x100: {
+ "tag_name": "optee_pageable_part",
+ "format": "Q",
+ "fields": ["pp_addr"],
+ },
+ 0x101: {
+ "tag_name": "dt_spmc_manifest",
+ },
+ 0x102: {
+ "tag_name": "exec_ep_info",
+ "format": "2BHIQI4x8Q",
+ "fields": ["ep_info"],
+ },
+ 0x104: {
+ "tag_name": "sram_layout",
+ "format": "2Q",
+ "fields": ["addr", "size"],
+ },
+}
+tag_name_to_tag_id = {
+ te["tag_name"]: tag_id for tag_id, te in transfer_entry_formats.items()
+}
+
class TransferList:
"""Class representing a Transfer List based on version 1.0 of the Firmware Handoff specification."""
@@ -96,6 +151,28 @@
return tl
+ @classmethod
+ def from_dict(cls, config: dict):
+ """Create a TL from data in a dictionary
+
+ The dictionary should have the same format as the yaml config files.
+ See the readme for more detail.
+
+ :param config: Dictionary containing the data described above.
+ """
+ # get settings from config and set defaults
+ max_size = config.get("max_size", 0x1000)
+ has_checksum = config.get("has_checksum", True)
+
+ flags = TRANSFER_LIST_ENABLE_CHECKSUM if has_checksum else 0
+
+ tl = cls(max_size, flags)
+
+ for entry in config["entries"]:
+ tl.add_transfer_entry_from_dict(entry)
+
+ return tl
+
def header_to_bytes(self) -> bytes:
return struct.pack(
self.encoding,
@@ -141,6 +218,106 @@
self.update_checksum()
return te
+ def add_transfer_entry_from_struct_format(
+ self, tag_id: int, struct_format: str, *args
+ ):
+ 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) -> "TransferEntry":
+ """Add entry_point_info transfer entry
+
+ :param entry: Dictionary of the transfer entry, in the same format as
+ the YAML file.
+ """
+ ep_info = entry["ep_info"]
+ header = ep_info["h"]
+
+ # size of the entry_point_info struct
+ entry_point_size = 88
+
+ attr = header["attr"]
+ if type(attr) is str:
+ # convert string of flags names to an integer
+
+ # bit number | 0 | 1 |
+ # ------------|-----------------------|----------------------|
+ # 0 | secure | non-secure |
+ # 1 | little endian | big-endian |
+ # 2 | disable secure timer | enable secure timer |
+ # 3 | executable | non-executable |
+ # 4 | first exe | not first exe |
+ #
+ # Bit 5 and bit 0 are used to determine the security state.
+
+ flag_names = {
+ "EP_SECURE": 0x0,
+ "EP_NON_SECURE": 0x1,
+ "EP_REALM": 0x21,
+ "EP_EE_LITTLE": 0x0,
+ "EP_EE_BIG": 0x2,
+ "EP_ST_DISABLE": 0x0,
+ "EP_ST_ENABLE": 0x4,
+ "EP_NON_EXECUTABLE": 0x0,
+ "EP_EXECUTABLE": 0x8,
+ "EP_FIRST_EXE": 0x10,
+ }
+
+ # create list of integer flags, then bitwise-or them together
+ flags = [flag_names[f.strip()] for f in attr.split("|")]
+ attr = reduce(lambda x, y: x | y, flags)
+
+ return self.add_transfer_entry_from_struct_format(
+ 0x102,
+ transfer_entry_formats[0x102]["format"],
+ header["type"],
+ header["version"],
+ entry_point_size,
+ attr,
+ ep_info["pc"],
+ ep_info["spsr"],
+ *ep_info["args"],
+ )
+
+ def add_transfer_entry_from_dict(
+ self,
+ entry: dict,
+ ) -> "TransferEntry":
+ """Add a transfer entry from data in a dictionary
+
+ The dictionary should have the same format as the entries in the yaml
+ config files. See the readme for more detail.
+
+ :param entry: Dictionary containing the data described above.
+ """
+ # Tag_id is either a tag name or a tag id. Use it to get the TE format.
+ tag_id = entry["tag_id"]
+ if tag_id in tag_name_to_tag_id:
+ tag_id = tag_name_to_tag_id[tag_id]
+ te_format = transfer_entry_formats[tag_id]
+ tag_name = te_format["tag_name"]
+
+ if "blob_file_path" in entry:
+ return self.add_transfer_entry_from_file(tag_id, entry["blob_file_path"])
+ elif tag_name == "tpm_event_log_table":
+ with open(entry["event_log"], "rb") as f:
+ event_log_data = f.read()
+
+ flags_bytes = entry["flags"].to_bytes(4, "little")
+ data = flags_bytes + event_log_data
+
+ return self.add_transfer_entry(tag_id, data)
+ elif tag_name == "exec_ep_info":
+ return self.add_entry_point_info_transfer_entry(entry)
+ elif "format" in te_format and "fields" in te_format:
+ fields = [entry[field] for field in te_format["fields"]]
+ return self.add_transfer_entry_from_struct_format(
+ tag_id, te_format["format"], *fields
+ )
+ else:
+ raise ValueError(f"Invalid transfer entry {entry}.")
+
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())