From 311209934e78b1d7005ae48c95b0d45c08c1c728 Mon Sep 17 00:00:00 2001 From: Charlie Bareham Date: Mon, 17 Jun 2024 11:58:03 +0100 Subject: [PATCH 1/4] feat: add creating transfer lists from yaml files This commit adds a command create-from-yaml to tlc, which creates a transfer list from a yaml file. It also changes the files structure of the fixtures in the unit tests so they are in a directory called trusted-firmware-a. This is necessary because blob file paths in the yaml file are relative to the root of TF-A. The blob files are not verified by TLC, so it can be used to load arbitrary binary information into the transfer list. The authenticity of the transfer list must be ensured by the loader. Change-Id: Idf704ce5d9b7e28b31f471ac337e4aef33d0ad8a Signed-off-by: Charlie Bareham --- docs/tools/transfer-list-compiler.rst | 81 ++++++++++++ poetry.lock | 19 +-- tools/tlc/assets/images/coverage.svg | 4 +- tools/tlc/poetry.lock | 25 ++-- tools/tlc/pyproject.toml | 1 + tools/tlc/tests/conftest.py | 32 +++++ tools/tlc/tests/test_cli.py | 180 ++++++++++++++++++++++++++ tools/tlc/tlc/cli.py | 26 +++- tools/tlc/tlc/tl.py | 142 ++++++++++++++++++++ 9 files changed, 480 insertions(+), 30 deletions(-) diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst index c8ef7ac02..e9710d745 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,6 +192,81 @@ 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 + -------------- *Copyright (c) 2024, Arm Limited. All rights reserved.* diff --git a/poetry.lock b/poetry.lock index 31590deff..169bd4056 100644 --- a/poetry.lock +++ b/poetry.lock @@ -251,13 +251,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.1.0" +version = "8.2.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.1.0-py3-none-any.whl", hash = "sha256:3cd29f739ed65973840b068e3132135ce954c254d48b5b640484467ef7ab3c8c"}, - {file = "importlib_metadata-8.1.0.tar.gz", hash = "sha256:fcdcb1d5ead7bdf3dd32657bb94ebe9d2aabfe89a19782ddc32da5041d6ebfb4"}, + {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, + {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, ] [package.dependencies] @@ -447,13 +447,13 @@ files = [ [[package]] name = "pip" -version = "24.1.2" +version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, - {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, + {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, + {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, ] [[package]] @@ -645,13 +645,13 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "setuptools" -version = "71.1.0" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] @@ -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..5c1035cd8 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,180 @@ 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, + }, + ], +) +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. + """ + # 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" + ), + ), + ], +) +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..dae7e14f6 100644 --- a/tools/tlc/tlc/tl.py +++ b/tools/tlc/tlc/tl.py @@ -19,6 +19,60 @@ 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 +150,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 +217,72 @@ 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 + + return self.add_transfer_entry_from_struct_format( + 0x102, + transfer_entry_formats[0x102]["format"], + header["type"], + header["version"], + entry_point_size, + header["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 = entry["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()) From 792e8e896f81fff3e0d75dca5f633903fa18f55e Mon Sep 17 00:00:00 2001 From: Charlie Bareham Date: Thu, 20 Jun 2024 11:32:29 +0100 Subject: [PATCH 2/4] feat: add option to input text instead of tag id number Change-Id: I6d1b1a20d1cd5b073d7d614da102b9e6bd8ea522 Signed-off-by: Charlie Bareham --- docs/tools/transfer-list-compiler.rst | 9 +++++++++ tools/tlc/tests/test_cli.py | 7 ++++++- tools/tlc/tlc/tl.py | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst index e9710d745..8ecbab529 100644 --- a/docs/tools/transfer-list-compiler.rst +++ b/docs/tools/transfer-list-compiler.rst @@ -267,9 +267,18 @@ info entry would have an entry like: 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 + -------------- *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/tools/tlc/tests/test_cli.py b/tools/tlc/tests/test_cli.py index 5c1035cd8..6d50ab7cd 100644 --- a/tools/tlc/tests/test_cli.py +++ b/tools/tlc/tests/test_cli.py @@ -240,13 +240,18 @@ def test_create_entry_from_yaml_and_blob_file( "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. + in the yaml file must be integers (except for the tag IDs, which can be + strings). """ # create yaml config file config = { diff --git a/tools/tlc/tlc/tl.py b/tools/tlc/tlc/tl.py index dae7e14f6..eec5db08e 100644 --- a/tools/tlc/tlc/tl.py +++ b/tools/tlc/tlc/tl.py @@ -259,7 +259,10 @@ class TransferList: :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"] From 4dcbba98cee2260e4c4f680f6a7fda5a98fdc7d5 Mon Sep 17 00:00:00 2001 From: Charlie Bareham Date: Thu, 20 Jun 2024 12:11:53 +0100 Subject: [PATCH 3/4] feat: add option to input attr as string of flag names Change-Id: I56f0364ef43c9d415a335474e15b68e79db37f5d Signed-off-by: Charlie Bareham --- docs/tools/transfer-list-compiler.rst | 27 +++++++++++++++++++++ tools/tlc/tests/test_cli.py | 23 ++++++++++++++++++ tools/tlc/tlc/tl.py | 34 ++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst index 8ecbab529..fa660dc6b 100644 --- a/docs/tools/transfer-list-compiler.rst +++ b/docs/tools/transfer-list-compiler.rst @@ -275,6 +275,33 @@ You can give the name of the tag instead of the tag id number. The valid tag nam * 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.* diff --git a/tools/tlc/tests/test_cli.py b/tools/tlc/tests/test_cli.py index 6d50ab7cd..99b5816b7 100644 --- a/tools/tlc/tests/test_cli.py +++ b/tools/tlc/tests/test_cli.py @@ -320,6 +320,29 @@ def test_create_from_yaml_check_sum_bytes(tlcrunner, tmpyamlconfig, tmptlstr, en "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( diff --git a/tools/tlc/tlc/tl.py b/tools/tlc/tlc/tl.py index eec5db08e..3f0065d05 100644 --- a/tools/tlc/tlc/tl.py +++ b/tools/tlc/tlc/tl.py @@ -13,6 +13,7 @@ import typing import math import struct from dataclasses import dataclass +from functools import reduce from pathlib import Path from tlc.te import TransferEntry @@ -236,13 +237,44 @@ class TransferList: # 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, - header["attr"], + attr, ep_info["pc"], ep_info["spsr"], *ep_info["args"], From fe94a21a6815fc8623074e7184d87583f2f58940 Mon Sep 17 00:00:00 2001 From: Harrison Mutai Date: Fri, 12 Jul 2024 14:23:02 +0000 Subject: [PATCH 4/4] fix(arm): move HW_CONFIG relocation into BL31 Refactor DT relocation logic from BL2 to BL31 for non-secure DRAM. Previously, BL2 was responsible for copying the DT into SRAM and DRAM, resulting in duplicate code in BL31 to cater for the `RESET_TO_BL31` case. By moving the re-location logic to BL31, we simplify handling of the non-secure DT and TL. Change-Id: Id239f9410669afe4b223fa8d8bb093084a0e5e1b Signed-off-by: Harrison Mutai --- include/plat/arm/common/plat_arm.h | 5 +-- plat/arm/common/arm_bl2_setup.c | 15 ++------ plat/arm/common/arm_bl31_setup.c | 60 +++++++++++++++++++++++------ plat/arm/common/arm_transfer_list.c | 24 +----------- 4 files changed, 54 insertions(+), 50 deletions(-) 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 632e84c2d..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) { @@ -142,8 +149,8 @@ void __init arm_bl31_early_platform_setup(u_register_t arg0, u_register_t arg1, bl33_image_ep_info.args.arg0 = FW_NS_HANDOFF_BASE + ARM_PRELOADED_DTB_OFFSET; - bl33_image_ep_info.args.arg1 = TRANSFER_LIST_SIGNATURE | - REGISTER_CONVENTION_VERSION_MASK; + bl33_image_ep_info.args.arg1 = + TRANSFER_LIST_HANDOFF_X1_VALUE(REGISTER_CONVENTION_VERSION); bl33_image_ep_info.args.arg3 = FW_NS_HANDOFF_BASE; #else struct transfer_list_entry *te = NULL; @@ -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); -}