Browse Source

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
pull/1996/merge
Manish Pandey 3 months ago
committed by TrustedFirmware Code Review
parent
commit
9bfad24c3b
  1. 117
      docs/tools/transfer-list-compiler.rst
  2. 5
      include/plat/arm/common/plat_arm.h
  3. 15
      plat/arm/common/arm_bl2_setup.c
  4. 56
      plat/arm/common/arm_bl31_setup.c
  5. 24
      plat/arm/common/arm_transfer_list.c
  6. 1
      poetry.lock
  7. 4
      tools/tlc/assets/images/coverage.svg
  8. 25
      tools/tlc/poetry.lock
  9. 1
      tools/tlc/pyproject.toml
  10. 32
      tools/tlc/tests/conftest.py
  11. 208
      tools/tlc/tests/test_cli.py
  12. 24
      tools/tlc/tlc/cli.py
  13. 177
      tools/tlc/tlc/tl.py

117
docs/tools/transfer-list-compiler.rst

@ -59,6 +59,12 @@ through the ``--entry`` option.
provided tag ID. It only checks that the tags provided as input are within 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. 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 Printing the contents of a TL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -186,9 +192,120 @@ performs the following checks:
#. Ensures that the specified version is greater than or equal to the tool’s current version. #. Ensures that the specified version is greater than or equal to the tool’s current version.
#. Verifies alignment criteria for all TE’s. #. 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.* *Copyright (c) 2024, Arm Limited. All rights reserved.*
.. _Firmware Handoff specification: https://github.com/FirmwareHandoff/firmware_handoff/ .. _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/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

5
include/plat/arm/common/plat_arm.h

@ -284,10 +284,7 @@ void arm_bl31_plat_arch_setup(void);
/* Firmware Handoff utility functions */ /* Firmware Handoff utility functions */
void arm_transfer_list_dyn_cfg_init(struct transfer_list_header *secure_tl); 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, 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 *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);
/* TSP utility functions */ /* TSP utility functions */
void arm_tsp_early_platform_setup(void); void arm_tsp_early_platform_setup(void);

15
plat/arm/common/arm_bl2_setup.c

@ -162,16 +162,6 @@ void arm_bl2_platform_setup(void)
#if defined(PLAT_ARM_MEM_PROT_ADDR) #if defined(PLAT_ARM_MEM_PROT_ADDR)
arm_nor_psci_do_static_mem_protect(); arm_nor_psci_do_static_mem_protect();
#endif #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) void bl2_platform_setup(void)
@ -326,7 +316,8 @@ int arm_bl2_plat_handle_post_image_load(unsigned int image_id)
#if TRANSFER_LIST #if TRANSFER_LIST
if (image_id == HW_CONFIG_ID) { 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 */ #endif /* TRANSFER_LIST */
@ -340,5 +331,5 @@ void arm_bl2_setup_next_ep_info(bl_mem_params_node_t *next_param_node)
&next_param_node->ep_info); &next_param_node->ep_info);
assert(ep != NULL); 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);
} }

56
plat/arm/common/arm_bl31_setup.c

@ -25,6 +25,8 @@
#include <platform_def.h> #include <platform_def.h>
static struct transfer_list_header *secure_tl __unused; 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 * Placeholder variables for copying the arguments that have been passed to
* BL31 from BL2. * BL31 from BL2.
@ -95,7 +97,12 @@ struct entry_point_info *bl31_plat_get_next_image_ep_info(uint32_t type)
assert(sec_state_is_valid(type)); assert(sec_state_is_valid(type));
if (type == NON_SECURE) { 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; next_image_info = &bl33_image_ep_info;
#endif
} }
#if ENABLE_RME #if ENABLE_RME
else if (type == REALM) { else if (type == REALM) {
@ -357,6 +364,28 @@ void bl31_early_platform_setup2(u_register_t arg0, u_register_t arg1,
******************************************************************************/ ******************************************************************************/
void arm_bl31_platform_setup(void) 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 */ /* Initialize the GIC driver, cpu and distributor interfaces */
plat_arm_gic_driver_init(); plat_arm_gic_driver_init();
plat_arm_gic_init(); plat_arm_gic_init();
@ -399,9 +428,26 @@ void arm_bl31_platform_setup(void)
******************************************************************************/ ******************************************************************************/
void arm_bl31_plat_runtime_setup(void) void arm_bl31_plat_runtime_setup(void)
{ {
struct transfer_list_entry *te __unused;
/* Initialize the runtime console */ /* Initialize the runtime console */
arm_console_runtime_init(); 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 #if RECLAIM_INIT_CODE
arm_free_init_memory(); arm_free_init_memory();
#endif #endif
@ -516,15 +562,5 @@ void __init arm_bl31_plat_arch_setup(void)
void __init bl31_plat_arch_setup(void) void __init bl31_plat_arch_setup(void)
{ {
struct transfer_list_entry *te __unused;
arm_bl31_plat_arch_setup(); 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
} }

24
plat/arm/common/arm_transfer_list.c

@ -30,8 +30,7 @@ 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, 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 *secure_tl)
struct transfer_list_header *ns_tl)
{ {
uint32_t next_exe_img_id; uint32_t next_exe_img_id;
entry_point_info_t *ep; entry_point_info_t *ep;
@ -53,10 +52,7 @@ void arm_transfer_list_populate_ep_info(bl_mem_params_node_t *next_param_node,
ep = transfer_list_entry_data(te); ep = transfer_list_entry_data(te);
if (next_exe_img_id == BL33_IMAGE_ID) { if ((next_exe_img_id == BL32_IMAGE_ID) && SPMC_AT_EL3) {
ep = transfer_list_set_handoff_args(ns_tl, ep);
assert(ep != NULL);
} else if ((next_exe_img_id == BL32_IMAGE_ID) && SPMC_AT_EL3) {
/* /*
* Populate the BL32 image base, size and max limit in * Populate the BL32 image base, size and max limit in
* the entry point information, since there is no * the entry point information, since there is no
@ -78,19 +74,3 @@ void arm_transfer_list_populate_ep_info(bl_mem_params_node_t *next_param_node,
flush_dcache_range((uintptr_t)secure_tl, secure_tl->size); 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);
}

1
poetry.lock

@ -893,6 +893,7 @@ develop = false
[package.dependencies] [package.dependencies]
click = "^8.1.7" click = "^8.1.7"
pyyaml = "^6.0.1"
rich = "^10.14.0" rich = "^10.14.0"
typer = {version = "^0.4.0", extras = ["all"]} typer = {version = "^0.4.0", extras = ["all"]}

4
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"> <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="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="31.5" y="14">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="15" fill="#010101" fill-opacity=".3">95%</text>
<text x="80" y="14">97%</text> <text x="80" y="14">95%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 901 B

After

Width:  |  Height:  |  Size: 901 B

25
tools/tlc/poetry.lock

@ -386,13 +386,13 @@ pipenv = ["pipenv (<=2022.12.19)"]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.2.1" version = "1.2.2"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
] ]
[package.extras] [package.extras]
@ -1107,18 +1107,19 @@ gitlab = ["python-gitlab (>=1.3.0)"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "70.2.0" version = "72.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"}, {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"},
{file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"}, {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"},
] ]
[package.extras] [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"] 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]] [[package]]
name = "shellingham" name = "shellingham"
@ -1191,13 +1192,13 @@ files = [
[[package]] [[package]]
name = "tomlkit" name = "tomlkit"
version = "0.12.5" version = "0.13.0"
description = "Style preserving TOML library" description = "Style preserving TOML library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"},
{file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"},
] ]
[[package]] [[package]]
@ -1352,4 +1353,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "60bdb4a8b67815f01b4e7089d9f7664afcb9041fa8adf5aa92d977f4e2d5b4b2" content-hash = "cfcb196cda412f6139302937640455aa8154d7979c69017fe45ddd528e4a1bf2"

1
tools/tlc/pyproject.toml

@ -37,6 +37,7 @@ python = "^3.8"
typer = {extras = ["all"], version = "^0.4.0"} typer = {extras = ["all"], version = "^0.4.0"}
rich = "^10.14.0" rich = "^10.14.0"
click = "^8.1.7" click = "^8.1.7"
pyyaml = "^6.0.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
bandit = "^1.7.1" bandit = "^1.7.1"

32
tools/tlc/tests/conftest.py

@ -10,6 +10,7 @@
""" Common configurations and fixtures for test environment.""" """ Common configurations and fixtures for test environment."""
import pytest import pytest
import yaml
from click.testing import CliRunner from click.testing import CliRunner
from tlc.cli import cli from tlc.cli import cli
@ -20,6 +21,11 @@ def tmptlstr(tmpdir):
return tmpdir.join("tl.bin").strpath return tmpdir.join("tl.bin").strpath
@pytest.fixture
def tmpyamlconfig(tmpdir):
return tmpdir.join("config.yaml").strpath
@pytest.fixture @pytest.fixture
def tmpfdt(tmpdir): def tmpfdt(tmpdir):
fdt = tmpdir.join("fdt.dtb") fdt = tmpdir.join("fdt.dtb")
@ -27,6 +33,32 @@ def tmpfdt(tmpdir):
return fdt 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 @pytest.fixture
def tlcrunner(tmptlstr): def tlcrunner(tmptlstr):
runner = CliRunner() runner = CliRunner()

208
tools/tlc/tests/test_cli.py

@ -11,8 +11,11 @@
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
from math import log2, ceil
import pytest import pytest
import pytest
import yaml
from click.testing import CliRunner from click.testing import CliRunner
from tlc.cli import cli from tlc.cli import cli
@ -203,3 +206,208 @@ def test_validate_unsupported_version(version, tmptlstr, tlcrunner, monkeypatch)
assert result.exit_code == 0 assert result.exit_code == 0
else: else:
assert result.exit_code == 1 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

24
tools/tlc/tlc/cli.py

@ -12,6 +12,7 @@
from pathlib import Path from pathlib import Path
import click import click
import yaml
from tlc.tl import * from tlc.tl import *
@ -44,15 +45,26 @@ def cli():
show_default=True, show_default=True,
help="Settings for the TL's properties.", 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.""" """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: entry = (*entry, (1, fdt)) if fdt else entry
for id, path in entry:
tl.add_transfer_entry_from_file(id, path) for id, path in entry:
tl.add_transfer_entry_from_file(id, path)
except MemoryError as mem_excp: except MemoryError as mem_excp:
raise MemoryError( raise MemoryError(
"TL max size exceeded, consider increasing with the option -s" "TL max size exceeded, consider increasing with the option -s"

177
tools/tlc/tlc/tl.py

@ -13,12 +13,67 @@ import typing
import math import math
import struct import struct
from dataclasses import dataclass from dataclasses import dataclass
from functools import reduce
from pathlib import Path from pathlib import Path
from tlc.te import TransferEntry from tlc.te import TransferEntry
TRANSFER_LIST_ENABLE_CHECKSUM = 0b1 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 TransferList:
"""Class representing a Transfer List based on version 1.0 of the Firmware Handoff specification.""" """Class representing a Transfer List based on version 1.0 of the Firmware Handoff specification."""
@ -96,6 +151,28 @@ class TransferList:
return tl 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: def header_to_bytes(self) -> bytes:
return struct.pack( return struct.pack(
self.encoding, self.encoding,
@ -141,6 +218,106 @@ class TransferList:
self.update_checksum() self.update_checksum()
return te 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": def add_transfer_entry_from_file(self, tag_id: int, path: Path) -> "TransferEntry":
with open(path, "rb") as f: with open(path, "rb") as f:
return self.add_transfer_entry(tag_id, f.read()) return self.add_transfer_entry(tag_id, f.read())

Loading…
Cancel
Save