diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst index c8ef7ac02..fa660dc6b 100644 --- a/docs/tools/transfer-list-compiler.rst +++ b/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 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 @@ performs the following checks: #. 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 ba8df2a87..3ea29eb14 100644 --- a/include/plat/arm/common/plat_arm.h +++ b/include/plat/arm/common/plat_arm.h @@ -284,10 +284,7 @@ void arm_bl31_plat_arch_setup(void); /* 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 99243dc87..b5a7db140 100644 --- a/plat/arm/common/arm_bl2_setup.c +++ b/plat/arm/common/arm_bl2_setup.c @@ -162,16 +162,6 @@ void arm_bl2_platform_setup(void) #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 @@ int arm_bl2_plat_handle_post_image_load(unsigned int image_id) #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 @@ void arm_bl2_setup_next_ep_info(bl_mem_params_node_t *next_param_node) &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 c7dbb7a3b..e91746b9c 100644 --- a/plat/arm/common/arm_bl31_setup.c +++ b/plat/arm/common/arm_bl31_setup.c @@ -25,6 +25,8 @@ #include 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 @@ struct entry_point_info *bl31_plat_get_next_image_ep_info(uint32_t type) 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 bl31_early_platform_setup2(u_register_t arg0, u_register_t arg1, ******************************************************************************/ 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_platform_setup(void) ******************************************************************************/ 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 arm_bl31_plat_arch_setup(void) 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 d144bbb5b..59fb03984 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_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) + struct transfer_list_header *secure_tl) { uint32_t next_exe_img_id; 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); - 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 @@ 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); } - -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 d72e14a50..b465f4858 100644 --- a/poetry.lock +++ b/poetry.lock @@ -893,6 +893,7 @@ develop = false [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 34387324e..b6c4e361f 100644 --- a/tools/tlc/assets/images/coverage.svg +++ b/tools/tlc/assets/images/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 97% - 97% + 95% + 95% diff --git a/tools/tlc/poetry.lock b/tools/tlc/poetry.lock index 5a39322c5..839f2364c 100644 --- a/tools/tlc/poetry.lock +++ b/tools/tlc/poetry.lock @@ -386,13 +386,13 @@ pipenv = ["pipenv (<=2022.12.19)"] [[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 @@ gitlab = ["python-gitlab (>=1.3.0)"] [[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 @@ files = [ [[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 @@ files = [ [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 30875a522..5661abfd5 100644 --- a/tools/tlc/pyproject.toml +++ b/tools/tlc/pyproject.toml @@ -37,6 +37,7 @@ python = "^3.8" 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 6b28e432b..b8f88b50c 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 @@ -20,6 +21,11 @@ def tmptlstr(tmpdir): return tmpdir.join("tl.bin").strpath +@pytest.fixture +def tmpyamlconfig(tmpdir): + return tmpdir.join("config.yaml").strpath + + @pytest.fixture def tmpfdt(tmpdir): fdt = tmpdir.join("fdt.dtb") @@ -27,6 +33,32 @@ def tmpfdt(tmpdir): 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 d79773b07..99b5816b7 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 @@ def test_validate_unsupported_version(version, tmptlstr, tlcrunner, monkeypatch) 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 e574e59da..1d4949d67 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 @@ def cli(): 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) - - entry = (*entry, (1, fdt)) if fdt else entry - try: - for id, path in entry: - tl.add_transfer_entry_from_file(id, path) + if from_yaml: + with open(from_yaml, "r") as f: + config = yaml.safe_load(f) + + tl = TransferList.from_dict(config) + else: + tl = TransferList(size) + + 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 a409651f1..3f0065d05 100644 --- a/tools/tlc/tlc/tl.py +++ b/tools/tlc/tlc/tl.py @@ -13,12 +13,67 @@ import typing 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 @@ class TransferList: 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 @@ class TransferList: 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())