[PATCH v9 07/14] binman: Support new op-tee binary format
Simon Glass
sjg at chromium.org
Sat Jan 7 22:07:14 CET 2023
OP-TEE has a format with a binary header that can be used instead of the
ELF file. With newer versions of OP-TEE this may be required on some
platforms.
Add support for this in binman. First, add a method to obtain the ELF
sections from an entry, then use that in the FIT support. We then end up
with the ability to support both types of OP-TEE files, depending on which
one is passed in with the entry argument (TEE=xxx in the U-Boot build).
Signed-off-by: Simon Glass <sjg at chromium.org>
---
(no changes since v8)
Changes in v8:
- Fix 'anb', 'provide' and 'second' typos
- Don't silence the return code from tee_os.ObtainContents()
- Rename is_optee_bin()
- Improve various comments
Changes in v7:
- Correct missing test coverage
Changes in v6:
- Update op-tee to support new v1 binary header
tools/binman/entries.rst | 37 ++++++++-
tools/binman/entry.py | 13 +++
tools/binman/etype/fit.py | 72 +++++++++--------
tools/binman/etype/section.py | 9 +++
tools/binman/etype/tee_os.py | 76 +++++++++++++++++-
tools/binman/ftest.py | 83 ++++++++++++++++++++
tools/binman/test/263_tee_os_opt.dts | 22 ++++++
tools/binman/test/264_tee_os_opt_fit.dts | 33 ++++++++
tools/binman/test/265_tee_os_opt_fit_bad.dts | 40 ++++++++++
9 files changed, 352 insertions(+), 33 deletions(-)
create mode 100644 tools/binman/test/263_tee_os_opt.dts
create mode 100644 tools/binman/test/264_tee_os_opt_fit.dts
create mode 100644 tools/binman/test/265_tee_os_opt_fit_bad.dts
diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index 5b9eb8b82c7..f6cc8003853 100644
--- a/tools/binman/entries.rst
+++ b/tools/binman/entries.rst
@@ -1508,12 +1508,47 @@ Entry: tee-os: Entry containing an OP-TEE Trusted OS (TEE) blob
Properties / Entry arguments:
- tee-os-path: Filename of file to read into entry. This is typically
- called tee-pager.bin
+ called tee.bin or tee.elf
This entry holds the run-time firmware, typically started by U-Boot SPL.
See the U-Boot README for your architecture or board for how to use it. See
https://github.com/OP-TEE/optee_os for more information about OP-TEE.
+Note that if the file is in ELF format, it must go in a FIT. In that case,
+this entry will mark itself as absent, providing the data only through the
+read_elf_segments() method.
+
+Marking this entry as absent means that it if is used in the wrong context
+it can be automatically dropped. Thus it is possible to add an OP-TEE entry
+like this::
+
+ binman {
+ tee-os {
+ };
+ };
+
+and pass either an ELF or plain binary in with -a tee-os-path <filename>
+and have binman do the right thing:
+
+ - include the entry if tee.bin is provided and it does NOT have the v1
+ header
+ - drop it otherwise
+
+When used within a FIT, we can do::
+
+ binman {
+ fit {
+ tee-os {
+ };
+ };
+ };
+
+which will split the ELF into separate nodes for each segment, if an ELF
+file is provided (see :ref:`etype_fit`), or produce a single node if the
+OP-TEE binary v1 format is provided (see optee_doc_) .
+
+.. _optee_doc: https://optee.readthedocs.io/en/latest/architecture/core.html#partitioning-of-the-binary
+
.. _etype_text:
diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index 637aece3705..de51d295891 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -1290,3 +1290,16 @@ features to produce new behaviours.
def mark_absent(self, msg):
tout.info("Entry '%s' marked absent: %s" % (self._node.path, msg))
self.absent = True
+
+ def read_elf_segments(self):
+ """Read segments from an entry that can generate an ELF file
+
+ Returns:
+ tuple:
+ list of segments, each:
+ int: Segment number (0 = first)
+ int: Start address of segment in memory
+ bytes: Contents of segment
+ int: entry address of ELF file
+ """
+ return None
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 8ad4f3a8a83..fea3adcc68d 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -540,41 +540,35 @@ class Entry_fit(Entry_section):
else:
self.Raise("Generator node requires 'fit,fdt-list' property")
- def _gen_split_elf(base_node, node, elf_data, missing):
+ def _gen_split_elf(base_node, node, segments, entry_addr):
"""Add nodes for the ELF file, one per group of contiguous segments
Args:
base_node (Node): Template node from the binman definition
node (Node): Node to replace (in the FIT being built)
- data (bytes): ELF-format data to process (may be empty)
- missing (bool): True if any of the data is missing
-
+ segments (list): list of segments, each:
+ int: Segment number (0 = first)
+ int: Start address of segment in memory
+ bytes: Contents of segment
+ entry_addr (int): entry address of ELF file
"""
- # If any pieces are missing, skip this. The missing entries will
- # show an error
- if not missing:
- try:
- segments, entry = elf.read_loadable_segments(elf_data)
- except ValueError as exc:
- self._raise_subnode(node,
- f'Failed to read ELF file: {str(exc)}')
- for (seq, start, data) in segments:
- node_name = node.name[1:].replace('SEQ', str(seq + 1))
- with fsw.add_node(node_name):
- loadables.append(node_name)
- for pname, prop in node.props.items():
- if not pname.startswith('fit,'):
- fsw.property(pname, prop.bytes)
- elif pname == 'fit,load':
- fsw.property_u32('load', start)
- elif pname == 'fit,entry':
- if seq == 0:
- fsw.property_u32('entry', entry)
- elif pname == 'fit,data':
- fsw.property('data', bytes(data))
- elif pname != 'fit,operation':
- self._raise_subnode(
- node, f"Unknown directive '{pname}'")
+ for (seq, start, data) in segments:
+ node_name = node.name[1:].replace('SEQ', str(seq + 1))
+ with fsw.add_node(node_name):
+ loadables.append(node_name)
+ for pname, prop in node.props.items():
+ if not pname.startswith('fit,'):
+ fsw.property(pname, prop.bytes)
+ elif pname == 'fit,load':
+ fsw.property_u32('load', start)
+ elif pname == 'fit,entry':
+ if seq == 0:
+ fsw.property_u32('entry', entry_addr)
+ elif pname == 'fit,data':
+ fsw.property('data', bytes(data))
+ elif pname != 'fit,operation':
+ self._raise_subnode(
+ node, f"Unknown directive '{pname}'")
def _gen_node(base_node, node, depth, in_images, entry):
"""Generate nodes from a template
@@ -598,6 +592,8 @@ class Entry_fit(Entry_section):
depth (int): Current node depth (0 is the base 'fit' node)
in_images (bool): True if this is inside the 'images' node, so
that 'data' properties should be generated
+ entry (entry_Section): Entry for the section containing the
+ contents of this node
"""
oper = self._get_operation(base_node, node)
if oper == OP_GEN_FDT_NODES:
@@ -609,10 +605,24 @@ class Entry_fit(Entry_section):
missing_list = []
entry.ObtainContents()
entry.Pack(0)
- data = entry.GetData()
entry.CheckMissing(missing_list)
- _gen_split_elf(base_node, node, data, bool(missing_list))
+ # If any pieces are missing, skip this. The missing entries will
+ # show an error
+ if not missing_list:
+ segs = entry.read_elf_segments()
+ if segs:
+ segments, entry_addr = segs
+ else:
+ elf_data = entry.GetData()
+ try:
+ segments, entry_addr = (
+ elf.read_loadable_segments(elf_data))
+ except ValueError as exc:
+ self._raise_subnode(
+ node, f'Failed to read ELF file: {str(exc)}')
+
+ _gen_split_elf(base_node, node, segments, entry_addr)
def _add_node(base_node, depth, node):
"""Add nodes to the output FIT
diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index dcb7a062047..57bfee0b286 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -948,3 +948,12 @@ class Entry_section(Entry):
super().AddBintools(btools)
for entry in self._entries.values():
entry.AddBintools(btools)
+
+ def read_elf_segments(self):
+ entries = self.GetEntries()
+
+ # If the section only has one entry, see if it can provide ELF segments
+ if len(entries) == 1:
+ for entry in entries.values():
+ return entry.read_elf_segments()
+ return None
diff --git a/tools/binman/etype/tee_os.py b/tools/binman/etype/tee_os.py
index 6ce4b672de4..5529727e833 100644
--- a/tools/binman/etype/tee_os.py
+++ b/tools/binman/etype/tee_os.py
@@ -4,19 +4,93 @@
# Entry-type module for OP-TEE Trusted OS firmware blob
#
+import struct
+
from binman.etype.blob_named_by_arg import Entry_blob_named_by_arg
+from binman import elf
class Entry_tee_os(Entry_blob_named_by_arg):
"""Entry containing an OP-TEE Trusted OS (TEE) blob
Properties / Entry arguments:
- tee-os-path: Filename of file to read into entry. This is typically
- called tee-pager.bin
+ called tee.bin or tee.elf
This entry holds the run-time firmware, typically started by U-Boot SPL.
See the U-Boot README for your architecture or board for how to use it. See
https://github.com/OP-TEE/optee_os for more information about OP-TEE.
+
+ Note that if the file is in ELF format, it must go in a FIT. In that case,
+ this entry will mark itself as absent, providing the data only through the
+ read_elf_segments() method.
+
+ Marking this entry as absent means that it if is used in the wrong context
+ it can be automatically dropped. Thus it is possible to add an OP-TEE entry
+ like this::
+
+ binman {
+ tee-os {
+ };
+ };
+
+ and pass either an ELF or plain binary in with -a tee-os-path <filename>
+ and have binman do the right thing:
+
+ - include the entry if tee.bin is provided and it does NOT have the v1
+ header
+ - drop it otherwise
+
+ When used within a FIT, we can do::
+
+ binman {
+ fit {
+ tee-os {
+ };
+ };
+ };
+
+ which will split the ELF into separate nodes for each segment, if an ELF
+ file is provided (see :ref:`etype_fit`), or produce a single node if the
+ OP-TEE binary v1 format is provided (see optee_doc_) .
+
+ .. _optee_doc: https://optee.readthedocs.io/en/latest/architecture/core.html#partitioning-of-the-binary
"""
def __init__(self, section, etype, node):
super().__init__(section, etype, node, 'tee-os')
self.external = True
+
+ @staticmethod
+ def is_optee_bin_v1(data):
+ return len(data) >= 8 and data[0:5] == b'OPTE\x01'
+
+ def ObtainContents(self, fake_size=0):
+ result = super().ObtainContents(fake_size)
+ if not self.missing:
+ # If using the flat binary (without the OP-TEE header), then it is
+ # just included as a blob. But if it is an ELF or usees the v1
+ # binary header, then the FIT implementation will call
+ # read_elf_segments() to get the segment information
+ if elf.is_valid(self.data):
+ self.mark_absent('uses Elf format which must be in a FIT')
+ elif self.is_optee_bin_v1(self.data):
+ # The FIT implementation will call read_elf_segments() to get
+ # the segment information
+ self.mark_absent('uses v1 format which must be in a FIT')
+ return result
+
+ def read_elf_segments(self):
+ data = self.GetData()
+ if self.is_optee_bin_v1(data):
+ # OP-TEE v1 format (tee.bin)
+ init_sz, start_hi, start_lo, _, paged_sz = (
+ struct.unpack_from('<5I', data, 0x8))
+ if paged_sz != 0:
+ self.Raise("OP-TEE paged mode not supported")
+ e_entry = (start_hi << 32) + start_lo
+ p_addr = e_entry
+ p_data = data[0x1c:]
+ if len(p_data) != init_sz:
+ self.Raise("Invalid OP-TEE file: size mismatch (expected %#x, have %#x)" %
+ (init_sz, len(p_data)))
+ return [[0, p_addr, p_data]], e_entry
+ return None
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index f47a745f1e1..f893050e706 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -112,6 +112,8 @@ REPACK_DTB_PROPS = ['orig-offset', 'orig-size']
# Supported compression bintools
COMP_BINTOOLS = ['bzip2', 'gzip', 'lz4', 'lzma_alone', 'lzop', 'xz', 'zstd']
+TEE_ADDR = 0x5678
+
class TestFunctional(unittest.TestCase):
"""Functional tests for binman
@@ -219,6 +221,9 @@ class TestFunctional(unittest.TestCase):
TestFunctional._MakeInputFile('tee.elf',
tools.read_file(cls.ElfTestFile('elf_sections')))
+ # Newer OP_TEE file in v1 binary format
+ cls.make_tee_bin('tee.bin')
+
cls.comp_bintools = {}
for name in COMP_BINTOOLS:
cls.comp_bintools[name] = bintool.Bintool.create(name)
@@ -644,6 +649,14 @@ class TestFunctional(unittest.TestCase):
def ElfTestFile(cls, fname):
return os.path.join(cls._elf_testdir, fname)
+ @classmethod
+ def make_tee_bin(cls, fname, paged_sz=0, extra_data=b''):
+ init_sz, start_hi, start_lo, dummy = (len(U_BOOT_DATA), 0, TEE_ADDR, 0)
+ data = b'OPTE\x01xxx' + struct.pack('<5I', init_sz, start_hi, start_lo,
+ dummy, paged_sz) + U_BOOT_DATA
+ data += extra_data
+ TestFunctional._MakeInputFile(fname, data)
+
def AssertInList(self, grep_list, target):
"""Assert that at least one of a list of things is in a target
@@ -6095,6 +6108,76 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
data = self._DoReadFile('262_absent.dts')
self.assertEqual(U_BOOT_DATA + U_BOOT_IMG_DATA, data)
+ def testPackTeeOsOptional(self):
+ """Test that an image with an optional TEE binary can be created"""
+ entry_args = {
+ 'tee-os-path': 'tee.elf',
+ }
+ data = self._DoReadFileDtb('263_tee_os_opt.dts',
+ entry_args=entry_args)[0]
+ self.assertEqual(U_BOOT_DATA + U_BOOT_IMG_DATA, data)
+
+ def checkFitTee(self, dts, tee_fname):
+ """Check that a tee-os entry works and returns data
+
+ Args:
+ dts (str): Device tree filename to use
+ tee_fname (str): filename containing tee-os
+
+ Returns:
+ bytes: Image contents
+ """
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+ entry_args = {
+ 'of-list': 'test-fdt1 test-fdt2',
+ 'default-dt': 'test-fdt2',
+ 'tee-os-path': tee_fname,
+ }
+ test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+ data = self._DoReadFileDtb(dts, entry_args=entry_args,
+ extra_indirs=[test_subdir])[0]
+ return data
+
+ def testFitTeeOsOptionalFit(self):
+ """Test an image with a FIT with an optional OP-TEE binary"""
+ data = self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bin')
+
+ # There should be only one node, holding the data set up in SetUpClass()
+ # for tee.bin
+ dtb = fdt.Fdt.FromData(data)
+ dtb.Scan()
+ node = dtb.GetNode('/images/tee-1')
+ self.assertEqual(TEE_ADDR,
+ fdt_util.fdt32_to_cpu(node.props['load'].value))
+ self.assertEqual(TEE_ADDR,
+ fdt_util.fdt32_to_cpu(node.props['entry'].value))
+ self.assertEqual(U_BOOT_DATA, node.props['data'].bytes)
+
+ def testFitTeeOsOptionalFitBad(self):
+ """Test an image with a FIT with an optional OP-TEE binary"""
+ with self.assertRaises(ValueError) as exc:
+ self.checkFitTee('265_tee_os_opt_fit_bad.dts', 'tee.bin')
+ self.assertIn(
+ "Node '/binman/fit': subnode 'images/@tee-SEQ': Failed to read ELF file: Magic number does not match",
+ str(exc.exception))
+
+ def testFitTeeOsBad(self):
+ """Test an OP-TEE binary with wrong formats"""
+ self.make_tee_bin('tee.bad1', 123)
+ with self.assertRaises(ValueError) as exc:
+ self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bad1')
+ self.assertIn(
+ "Node '/binman/fit/images/@tee-SEQ/tee-os': OP-TEE paged mode not supported",
+ str(exc.exception))
+
+ self.make_tee_bin('tee.bad2', 0, b'extra data')
+ with self.assertRaises(ValueError) as exc:
+ self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bad2')
+ self.assertIn(
+ "Node '/binman/fit/images/@tee-SEQ/tee-os': Invalid OP-TEE file: size mismatch (expected 0x4, have 0xe)",
+ str(exc.exception))
+
if __name__ == "__main__":
unittest.main()
diff --git a/tools/binman/test/263_tee_os_opt.dts b/tools/binman/test/263_tee_os_opt.dts
new file mode 100644
index 00000000000..2e4ec24ac2c
--- /dev/null
+++ b/tools/binman/test/263_tee_os_opt.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ tee-os {
+ /*
+ * this results in nothing being added since only the
+ * .bin format is supported by this etype, unless it is
+ * part of a FIT
+ */
+ };
+ u-boot-img {
+ };
+ };
+};
diff --git a/tools/binman/test/264_tee_os_opt_fit.dts b/tools/binman/test/264_tee_os_opt_fit.dts
new file mode 100644
index 00000000000..ae44b433edf
--- /dev/null
+++ b/tools/binman/test/264_tee_os_opt_fit.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ fit {
+ description = "test-desc";
+ #address-cells = <1>;
+ fit,fdt-list = "of-list";
+
+ images {
+ @tee-SEQ {
+ fit,operation = "split-elf";
+ description = "TEE";
+ type = "tee";
+ arch = "arm64";
+ os = "tee";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ tee-os {
+ };
+ };
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/265_tee_os_opt_fit_bad.dts b/tools/binman/test/265_tee_os_opt_fit_bad.dts
new file mode 100644
index 00000000000..7fa363cc199
--- /dev/null
+++ b/tools/binman/test/265_tee_os_opt_fit_bad.dts
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ fit {
+ description = "test-desc";
+ #address-cells = <1>;
+ fit,fdt-list = "of-list";
+
+ images {
+ @tee-SEQ {
+ fit,operation = "split-elf";
+ description = "TEE";
+ type = "tee";
+ arch = "arm64";
+ os = "tee";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ tee-os {
+ };
+
+ /*
+ * mess up the ELF data by adding
+ * another bit of data at the end
+ */
+ u-boot {
+ };
+ };
+ };
+ };
+ };
+};
--
2.39.0.314.g84b9a713c41-goog
More information about the U-Boot
mailing list