From c700f109a312d0c14b5409765013af4b7335d879 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Thu, 11 Nov 2021 08:14:18 +0100 Subject: binman: Fix extract command for using non-absolute image paths Otherwise the updated image will end up in the temporary folder that is purged after completion. Signed-off-by: Jan Kiszka Reviewed-by: Simon Glass --- tools/binman/control.py | 1 + 1 file changed, 1 insertion(+) (limited to 'tools') diff --git a/tools/binman/control.py b/tools/binman/control.py index 304fc70f56f..7da69ba38de 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -355,6 +355,7 @@ def ReplaceEntries(image_fname, input_fname, indir, entry_paths, Returns: List of EntryInfo records that were written """ + image_fname = os.path.abspath(image_fname) image = Image.FromFile(image_fname) # Replace an entry from a single file, as a special case -- cgit v1.2.3 From f0d4607d25639457f4a5e1af7c3037e427950fa4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 17 Nov 2021 20:15:06 +0300 Subject: tools/netconsole: Add support for socat socat is a very powerful tool to work with socets (and not only) in UNIX systems. Let's add support for it in netconsole. Signed-off-by: Andy Shevchenko Tested-by: Ferry Toth Reviewed-by: Ramon Fried --- tools/netconsole | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/netconsole b/tools/netconsole index 1a0ef22244e..155453320f7 100755 --- a/tools/netconsole +++ b/tools/netconsole @@ -34,7 +34,7 @@ if [ -z "${ip}" ] || [ -n "$4" ] ; then usage "Invalid number of arguments" fi -for nc in netcat nc ; do +for nc in socat netcat nc ; do type ${nc} >/dev/null 2>&1 && break done @@ -47,6 +47,10 @@ if type ncb 2>/dev/null ; then # see if ncb is in $PATH exec ncb ${board_out_port} +elif [ "${nc}" = "socat" ] ; then + # socat does support broadcast + while ${nc} STDIO "UDP4-LISTEN:${board_out_port}"; do :; done + elif [ -x ${0%/*}/ncb ] ; then # maybe it's in the same dir as the netconsole script exec ${0%/*}/ncb ${board_out_port} @@ -59,5 +63,9 @@ else fi ) & pid=$! -${nc} -u ${ip} ${board_in_port} +if [ "${nc}" = "socat" ] ; then + ${nc} - "UDP4:${ip}:${board_in_port}" +else + ${nc} -u ${ip} ${board_in_port} +fi kill ${pid} 2>/dev/null -- cgit v1.2.3 From ff139b6c70ff452df7f231627c796bdd259f6bbc Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:38 -0700 Subject: dtoc: Bring in the libfdt module automatically Use the same technique as with binman to load this module from the U-Boot tree if available. This allows running tests without having to specify the PYTHONPATH variable. Signed-off-by: Simon Glass --- tools/dtoc/test_fdt.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tools') diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index d104f3c7745..d86fc86187e 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -16,6 +16,12 @@ import unittest our_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(1, os.path.join(our_path, '..')) +# Bring in the libfdt module +sys.path.insert(2, 'scripts/dtc/pylibfdt') +sys.path.insert(2, os.path.join(our_path, '../../scripts/dtc/pylibfdt')) +sys.path.insert(2, os.path.join(our_path, + '../../build-sandbox_spl/scripts/dtc/pylibfdt')) + from dtoc import fdt from dtoc import fdt_util from dtoc.fdt_util import fdt32_to_cpu -- cgit v1.2.3 From d866e6291739de3da9550f2280574e1c44474dc3 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:39 -0700 Subject: dtoc: Add support for reading 64-bit ints Add functions to read a 64-bit integer property from the devicetree. Signed-off-by: Simon Glass --- tools/dtoc/fdt_util.py | 35 +++++++++++++++++++++++++++++++++++ tools/dtoc/test/dtoc_test_simple.dts | 1 + tools/dtoc/test_dtoc.py | 2 ++ tools/dtoc/test_fdt.py | 21 ++++++++++++++++++--- 4 files changed, 56 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py index 37e96b98642..51d0eb52423 100644 --- a/tools/dtoc/fdt_util.py +++ b/tools/dtoc/fdt_util.py @@ -27,6 +27,18 @@ def fdt32_to_cpu(val): """ return struct.unpack('>I', val)[0] +def fdt64_to_cpu(val): + """Convert a device tree cell to an integer + + Args: + val (list): Value to convert (list of 2 4-character strings representing + the cell value) + + Return: + int: A native-endian integer value + """ + return fdt32_to_cpu(val[0]) << 32 | fdt32_to_cpu(val[1]) + def fdt_cells_to_cpu(val, cells): """Convert one or two cells to a long integer @@ -108,6 +120,29 @@ def GetInt(node, propname, default=None): value = fdt32_to_cpu(prop.value) return value +def GetInt64(node, propname, default=None): + """Get a 64-bit integer from a property + + Args: + node (Node): Node object to read from + propname (str): property name to read + default (int): Default value to use if the node/property do not exist + + Returns: + int: value read, or default if none + + Raises: + ValueError: Property is not of the correct size + """ + prop = node.props.get(propname) + if not prop: + return default + if not isinstance(prop.value, list) or len(prop.value) != 2: + raise ValueError("Node '%s' property '%s' should be a list with 2 items for 64-bit values" % + (node.name, propname)) + value = fdt64_to_cpu(prop.value) + return value + def GetString(node, propname, default=None): """Get a string from a property diff --git a/tools/dtoc/test/dtoc_test_simple.dts b/tools/dtoc/test/dtoc_test_simple.dts index 5a6fa88d5cc..4c2c70af222 100644 --- a/tools/dtoc/test/dtoc_test_simple.dts +++ b/tools/dtoc/test/dtoc_test_simple.dts @@ -16,6 +16,7 @@ boolval; maybe-empty-int = <>; intval = <1>; + int64val = /bits/ 64 <0x123456789abcdef0>; intarray = <2 3 4>; byteval = [05]; bytearray = [06]; diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index 752061f27a4..ee17b8daf9a 100755 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -296,6 +296,7 @@ struct dtd_sandbox_spl_test { \tbool\t\tboolval; \tunsigned char\tbytearray[3]; \tunsigned char\tbyteval; +\tfdt32_t\t\tint64val[2]; \tfdt32_t\t\tintarray[3]; \tfdt32_t\t\tintval; \tunsigned char\tlongbytearray[9]; @@ -355,6 +356,7 @@ static struct dtd_sandbox_spl_test dtv_spl_test = { \t.boolval\t\t= true, \t.bytearray\t\t= {0x6, 0x0, 0x0}, \t.byteval\t\t= 0x5, +\t.int64val\t\t= {0x12345678, 0x9abcdef0}, \t.intarray\t\t= {0x2, 0x3, 0x4}, \t.intval\t\t\t= 0x1, \t.longbytearray\t\t= {0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index d86fc86187e..21a9a7ca063 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -24,7 +24,7 @@ sys.path.insert(2, os.path.join(our_path, from dtoc import fdt from dtoc import fdt_util -from dtoc.fdt_util import fdt32_to_cpu +from dtoc.fdt_util import fdt32_to_cpu, fdt64_to_cpu from fdt import Type, BytesToValue import libfdt from patman import command @@ -128,7 +128,7 @@ class TestFdt(unittest.TestCase): node = self.dtb.GetNode('/spl-test') props = self.dtb.GetProps(node) self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible', - 'intarray', 'intval', 'longbytearray', + 'int64val', 'intarray', 'intval', 'longbytearray', 'maybe-empty-int', 'notstring', 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'], sorted(props.keys())) @@ -335,6 +335,10 @@ class TestProp(unittest.TestCase): self.assertEqual(Type.INT, prop.type) self.assertEqual(1, fdt32_to_cpu(prop.value)) + prop = self._ConvertProp('int64val') + self.assertEqual(Type.INT, prop.type) + self.assertEqual(0x123456789abcdef0, fdt64_to_cpu(prop.value)) + prop = self._ConvertProp('intarray') self.assertEqual(Type.INT, prop.type) val = [fdt32_to_cpu(val) for val in prop.value] @@ -586,10 +590,21 @@ class TestFdtUtil(unittest.TestCase): self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) with self.assertRaises(ValueError) as e: - self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray')) + fdt_util.GetInt(self.node, 'intarray') self.assertIn("property 'intarray' has list value: expecting a single " 'integer', str(e.exception)) + def testGetInt64(self): + self.assertEqual(0x123456789abcdef0, + fdt_util.GetInt64(self.node, 'int64val')) + self.assertEqual(3, fdt_util.GetInt64(self.node, 'missing', 3)) + + with self.assertRaises(ValueError) as e: + fdt_util.GetInt64(self.node, 'intarray') + self.assertIn( + "property 'intarray' should be a list with 2 items for 64-bit values", + str(e.exception)) + def testGetString(self): self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) self.assertEqual('test', fdt_util.GetString(self.node, 'missing', -- cgit v1.2.3 From 40b4d647c606b6df7394333c1a58c10996c96a78 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:40 -0700 Subject: dtoc: Add support for reading fixed-length bytes properties Add functions to read a sequence of bytes from the devicetree. Signed-off-by: Simon Glass --- tools/dtoc/fdt_util.py | 20 ++++++++++++++++++++ tools/dtoc/test_fdt.py | 17 +++++++++++++++++ 2 files changed, 37 insertions(+) (limited to 'tools') diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py index 51d0eb52423..51bdbdcd3b2 100644 --- a/tools/dtoc/fdt_util.py +++ b/tools/dtoc/fdt_util.py @@ -202,6 +202,26 @@ def GetByte(node, propname, default=None): (node.name, propname, len(value), 1)) return ord(value[0]) +def GetBytes(node, propname, size, default=None): + """Get a set of bytes from a property + + Args: + node (Node): Node object to read from + propname (str): property name to read + size (int): Number of bytes to expect + default (bytes): Default value or None + + Returns: + bytes: Bytes value read, or default if none + """ + prop = node.props.get(propname) + if not prop: + return default + if len(prop.bytes) != size: + raise ValueError("Node '%s' property '%s' has length %d, expecting %d" % + (node.name, propname, len(prop.bytes), size)) + return prop.bytes + def GetPhandleList(node, propname): """Get a list of phandles from a property diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 21a9a7ca063..7a4c7efaa4a 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -635,6 +635,23 @@ class TestFdtUtil(unittest.TestCase): self.assertIn("property 'intval' has length 4, expecting 1", str(e.exception)) + def testGetBytes(self): + self.assertEqual(bytes([5]), fdt_util.GetBytes(self.node, 'byteval', 1)) + self.assertEqual(None, fdt_util.GetBytes(self.node, 'missing', 3)) + self.assertEqual( + bytes([3]), fdt_util.GetBytes(self.node, 'missing', 3, bytes([3]))) + + with self.assertRaises(ValueError) as e: + fdt_util.GetBytes(self.node, 'longbytearray', 7) + self.assertIn( + "Node 'spl-test' property 'longbytearray' has length 9, expecting 7", + str(e.exception)) + + self.assertEqual( + bytes([0, 0, 0, 1]), fdt_util.GetBytes(self.node, 'intval', 4)) + self.assertEqual( + bytes([3]), fdt_util.GetBytes(self.node, 'missing', 3, bytes([3]))) + def testGetPhandleList(self): dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts')) node = dtb.GetNode('/phandle-source2') -- cgit v1.2.3 From 650e5de7d407a3a9d2a9cc693eb8b1290a768d65 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:41 -0700 Subject: binman: Tidy up style in cmdline Update this file to improve the pylint score a little. The remaining item is: Function name "ParseArgs" doesn't conform to snake_case naming style which needs some binman-wide renaming. Signed-off-by: Simon Glass --- tools/binman/cmdline.py | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'tools') diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py index e73ff780956..14ec95c25be 100644 --- a/tools/binman/cmdline.py +++ b/tools/binman/cmdline.py @@ -2,18 +2,38 @@ # Copyright (c) 2016 Google, Inc # Written by Simon Glass # -# Command-line parser for binman -# + +"""Command-line parser for binman""" from argparse import ArgumentParser +def make_extract_parser(subparsers): + """make_extract_parser: Make a subparser for the 'extract' command + + Args: + subparsers (ArgumentParser): parser to add this one to + """ + extract_parser = subparsers.add_parser('extract', + help='Extract files from an image') + extract_parser.add_argument('-i', '--image', type=str, required=True, + help='Image filename to extract') + extract_parser.add_argument('-f', '--filename', type=str, + help='Output filename to write to') + extract_parser.add_argument('-O', '--outdir', type=str, default='', + help='Path to directory to use for output files') + extract_parser.add_argument('paths', type=str, nargs='*', + help='Paths within file to extract (wildcard)') + extract_parser.add_argument('-U', '--uncompressed', action='store_true', + help='Output raw uncompressed data for compressed entries') + def ParseArgs(argv): """Parse the binman command-line arguments Args: - argv: List of string arguments + argv (list of str): List of string arguments + Returns: - Tuple (options, args) with the command-line options and arugments. + tuple: (options, args) with the command-line options and arugments. options provides access to the options (e.g. option.debug) args is a list of string arguments """ @@ -74,8 +94,8 @@ controlled by a description in the board device tree.''' build_parser.add_argument('--update-fdt-in-elf', type=str, help='Update an ELF file with the output dtb: infile,outfile,begin_sym,end_sym') - entry_parser = subparsers.add_parser('entry-docs', - help='Write out entry documentation (see entries.rst)') + subparsers.add_parser( + 'entry-docs', help='Write out entry documentation (see entries.rst)') list_parser = subparsers.add_parser('ls', help='List files in an image') list_parser.add_argument('-i', '--image', type=str, required=True, @@ -83,18 +103,7 @@ controlled by a description in the board device tree.''' list_parser.add_argument('paths', type=str, nargs='*', help='Paths within file to list (wildcard)') - extract_parser = subparsers.add_parser('extract', - help='Extract files from an image') - extract_parser.add_argument('-i', '--image', type=str, required=True, - help='Image filename to extract') - extract_parser.add_argument('-f', '--filename', type=str, - help='Output filename to write to') - extract_parser.add_argument('-O', '--outdir', type=str, default='', - help='Path to directory to use for output files') - extract_parser.add_argument('paths', type=str, nargs='*', - help='Paths within file to extract (wildcard)') - extract_parser.add_argument('-U', '--uncompressed', action='store_true', - help='Output raw uncompressed data for compressed entries') + make_extract_parser(subparsers) replace_parser = subparsers.add_parser('replace', help='Replace entries in an image') -- cgit v1.2.3 From c475decf59a6460bbd706199d8157f2fd2c4f4fc Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:42 -0700 Subject: binman: Add a way to obtain the version Add a -V option which shows the version number of binman. For now this just uses a local 'version' file. Once the tool is packaged in some way we can figure out an approach that suits. Signed-off-by: Simon Glass --- tools/binman/cmdline.py | 29 +++++++++++++++++++++++++++++ tools/binman/ftest.py | 20 ++++++++++++++++++++ tools/binman/state.py | 18 ++++++++++++++++++ 3 files changed, 67 insertions(+) (limited to 'tools') diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py index 14ec95c25be..2229316f10e 100644 --- a/tools/binman/cmdline.py +++ b/tools/binman/cmdline.py @@ -5,7 +5,9 @@ """Command-line parser for binman""" +import argparse from argparse import ArgumentParser +import state def make_extract_parser(subparsers): """make_extract_parser: Make a subparser for the 'extract' command @@ -26,6 +28,32 @@ def make_extract_parser(subparsers): extract_parser.add_argument('-U', '--uncompressed', action='store_true', help='Output raw uncompressed data for compressed entries') + +#pylint: disable=R0903 +class BinmanVersion(argparse.Action): + """Handles the -V option to binman + + This reads the version information from a file called 'version' in the same + directory as this file. + + If not present it assumes this is running from the U-Boot tree and collects + the version from the Makefile. + + The format of the version information is three VAR = VALUE lines, for + example: + + VERSION = 2022 + PATCHLEVEL = 01 + EXTRAVERSION = -rc2 + """ + def __init__(self, nargs=0, **kwargs): + super().__init__(nargs=nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + parser._print_message(f'Binman {state.GetVersion()}\n') + parser.exit() + + def ParseArgs(argv): """Parse the binman command-line arguments @@ -59,6 +87,7 @@ controlled by a description in the board device tree.''' parser.add_argument('-v', '--verbosity', default=1, type=int, help='Control verbosity: 0=silent, 1=warnings, 2=notices, ' '3=info, 4=detail, 5=debug') + parser.add_argument('-V', '--version', nargs=0, action=BinmanVersion) subparsers = parser.add_subparsers(dest='cmd') subparsers.required = True diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 6be003786e8..6a36e8f3158 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -4661,6 +4661,26 @@ class TestFunctional(unittest.TestCase): str(e.exception), "Not enough space in '.*u_boot_binman_embed_sm' for data length.*") + def testVersion(self): + """Test we can get the binman version""" + version = '(unreleased)' + self.assertEqual(version, state.GetVersion(self._indir)) + + with self.assertRaises(SystemExit): + with test_util.capture_sys_output() as (_, stderr): + self._DoBinman('-V') + self.assertEqual('Binman %s\n' % version, stderr.getvalue()) + + # Try running the tool too, just to be safe + result = self._RunBinman('-V') + self.assertEqual('Binman %s\n' % version, result.stderr) + + # Set up a version file to make sure that works + version = 'v2025.01-rc2' + tools.WriteFile(os.path.join(self._indir, 'version'), version, + binary=False) + self.assertEqual(version, state.GetVersion(self._indir)) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/state.py b/tools/binman/state.py index 9e5b8a39310..af0a65e8418 100644 --- a/tools/binman/state.py +++ b/tools/binman/state.py @@ -16,6 +16,8 @@ import os from patman import tools from patman import tout +OUR_PATH = os.path.dirname(os.path.realpath(__file__)) + # Map an dtb etype to its expected filename DTB_TYPE_FNAME = { 'u-boot-spl-dtb': 'spl/u-boot-spl.dtb', @@ -515,3 +517,19 @@ def TimingShow(): for name, seconds in duration.items(): print('%10s: %10.1fms' % (name, seconds * 1000)) + +def GetVersion(path=OUR_PATH): + """Get the version string for binman + + Args: + path: Path to 'version' file + + Returns: + str: String version, e.g. 'v2021.10' + """ + version_fname = os.path.join(path, 'version') + if os.path.exists(version_fname): + version = tools.ReadFile(version_fname, binary=False) + else: + version = '(unreleased)' + return version -- cgit v1.2.3 From c47383114f8cbced067e10035b5b907e87425dd3 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:43 -0700 Subject: binman: Correct init of entry in Entry class This should not have an underscore. Drop it so that derived classes can rely on it being set correctly. Signed-off-by: Simon Glass --- tools/binman/entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 70222718ea9..5e66aa4fa54 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -95,7 +95,7 @@ class Entry(object): self.pad_after = 0 self.offset_unset = False self.image_pos = None - self._expand_size = False + self.expand_size = False self.compress = 'none' self.missing = False self.external = False -- cgit v1.2.3 From 557693ef7edaf66c43e709b33e7c98d7371c083c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:44 -0700 Subject: binman: Correct comments for ReadChildData() The comment here is incomplete. Fix it. Signed-off-by: Simon Glass --- tools/binman/entry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 5e66aa4fa54..2205bc8d923 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -860,7 +860,8 @@ features to produce new behaviours. """Handle writing the data in a child entry This should be called on the child's parent section after the child's - data has been updated. It + data has been updated. It should update any data structures needed to + validate that the update is successful. This base-class implementation does nothing, since the base Entry object does not have any children. @@ -870,7 +871,7 @@ features to produce new behaviours. Returns: True if the section could be updated successfully, False if the - data is such that the section could not updat + data is such that the section could not update """ return True -- cgit v1.2.3 From 1e99bc2923afa9c7a0e17895eec2d152c9187a80 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:45 -0700 Subject: binman: Drop the underscore in _ReadEntries() This function can be overridden so should not have an underscore. Drop it. Signed-off-by: Simon Glass --- tools/binman/etype/blob_phase.py | 2 +- tools/binman/etype/cbfs.py | 4 ++-- tools/binman/etype/files.py | 2 +- tools/binman/etype/section.py | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/blob_phase.py b/tools/binman/etype/blob_phase.py index 54ca54c50c1..ed25e467a13 100644 --- a/tools/binman/etype/blob_phase.py +++ b/tools/binman/etype/blob_phase.py @@ -48,4 +48,4 @@ class Entry_blob_phase(Entry_section): subnode = state.AddSubnode(self._node, name) # Read entries again, now that we have some - self._ReadEntries() + self.ReadEntries() diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 44db7b9bb20..0a858b8b849 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -171,7 +171,7 @@ class Entry_cbfs(Entry): self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86') self.align_default = None self._cbfs_entries = OrderedDict() - self._ReadSubnodes() + self.ReadEntries() self.reader = None def ObtainContents(self, skip=None): @@ -204,7 +204,7 @@ class Entry_cbfs(Entry): self.SetContents(data) return True - def _ReadSubnodes(self): + def ReadEntries(self): """Read the subnodes to find out what should go in this CBFS""" for node in self._node.subnodes: entry = Entry.Create(self, node) diff --git a/tools/binman/etype/files.py b/tools/binman/etype/files.py index 9b04a496a85..927d0f071df 100644 --- a/tools/binman/etype/files.py +++ b/tools/binman/etype/files.py @@ -64,4 +64,4 @@ class Entry_files(Entry_section): state.AddInt(subnode, 'align', self._files_align) # Read entries again, now that we have some - self._ReadEntries() + self.ReadEntries() diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index e2949fc9163..281a228cd0e 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -85,9 +85,9 @@ class Entry_section(Entry): if filename: self._filename = filename - self._ReadEntries() + self.ReadEntries() - def _ReadEntries(self): + def ReadEntries(self): for node in self._node.subnodes: if node.name.startswith('hash') or node.name.startswith('signature'): continue @@ -741,5 +741,5 @@ class Entry_section(Entry): missing: List of missing properties / entry args, each a string """ if not self._ignore_missing: - entry.Raise('Missing required properties/entry args: %s' % - (', '.join(missing))) + missing = ', '.join(missing) + entry.Raise(f'Missing required properties/entry args: {missing}') -- cgit v1.2.3 From b6caf0ebcabd20499f3171e12bcbee01a0b3d724 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:46 -0700 Subject: binman: Drop the filename property in entry_Section This is not used and does nothing. Drop it. Add a tweak to avoid reducing the pylint score. Signed-off-by: Simon Glass --- tools/binman/etype/section.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 281a228cd0e..952c01de186 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -81,9 +81,6 @@ class Entry_section(Entry): self._skip_at_start = 0 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix') self.align_default = fdt_util.GetInt(self._node, 'align-default', 0) - filename = fdt_util.GetString(self._node, 'filename') - if filename: - self._filename = filename self.ReadEntries() @@ -661,7 +658,7 @@ class Entry_section(Entry): return data def ReadChildData(self, child, decomp=True): - tout.Debug("ReadChildData for child '%s'" % child.GetPath()) + tout.Debug(f"ReadChildData for child '{child.GetPath()}'") parent_data = self.ReadData(True) offset = child.offset - self._skip_at_start tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" % -- cgit v1.2.3 From d34bcdd054d4c9ccbbbc49a267168e5ad1bc0e78 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:47 -0700 Subject: binman: Allow overriding BuildSectionData() This method is currently marked private. However it is useful to be able to subclass it, since much of the entry_Section code can be reused. Rename it. Also document one confusing part of this code, so people can understand how to add a test for this case. Fix up a few pylint warnings to avoid regressing the score. Signed-off-by: Simon Glass --- tools/binman/etype/section.py | 16 ++++++++++++---- tools/binman/ftest.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 952c01de186..334240384ea 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -182,7 +182,7 @@ class Entry_section(Entry): return data - def _BuildSectionData(self, required): + def BuildSectionData(self, required): """Build the contents of a section This places all entries at the right place, dealing with padding before @@ -190,6 +190,9 @@ class Entry_section(Entry): pad-before and pad-after properties in the section items) since that is handled by the parent section. + This should be overridden by subclasses which want to build their own + data structure for the section. + Args: required: True if the data must be present, False if it is OK to return None @@ -201,6 +204,9 @@ class Entry_section(Entry): for entry in self._entries.values(): entry_data = entry.GetData(required) + + # This can happen when this section is referenced from a collection + # earlier in the image description. See testCollectionSection(). if not required and entry_data is None: return None data = self.GetPaddedDataForEntry(entry, entry_data) @@ -250,7 +256,7 @@ class Entry_section(Entry): This excludes any padding. If the section is compressed, the compressed data is returned """ - data = self._BuildSectionData(required) + data = self.BuildSectionData(required) if data is None: return None self.SetContents(data) @@ -278,7 +284,7 @@ class Entry_section(Entry): self._SortEntries() self._ExpandEntries() - data = self._BuildSectionData(True) + data = self.BuildSectionData(True) self.SetContents(data) self.CheckSize() @@ -735,7 +741,9 @@ class Entry_section(Entry): nothing. Args: - missing: List of missing properties / entry args, each a string + entry (Entry): Entry to raise the error on + missing (list of str): List of missing properties / entry args, each + a string """ if not self._ignore_missing: missing = ', '.join(missing) diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 6a36e8f3158..3982560c47c 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -4533,7 +4533,7 @@ class TestFunctional(unittest.TestCase): def testCollectionSection(self): """Test a collection where a section must be built first""" # Sections never have their contents when GetData() is called, but when - # _BuildSectionData() is called with required=True, a section will force + # BuildSectionData() is called with required=True, a section will force # building the contents, producing an error is anything is still # missing. data = self._DoReadFile('199_collection_section.dts') -- cgit v1.2.3 From e586f44ea70867684426007ff9079389c6baddfe Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:48 -0700 Subject: binman: Allow control of which entries to read The ObtainContents() and GetEntryContents() methods in this file read every single entry in the section. This is the common case. However when one of the entries has had its data updated (e.g. with 'binman replace') we don't want to read it again from the file. Allow the entry to be skipped, for this purpose. This is currently done in the CBFS implementation, so adding it here will allow that to use more of the entry_Section code. Signed-off-by: Simon Glass --- tools/binman/etype/section.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 334240384ea..76e5eb19648 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -143,8 +143,8 @@ class Entry_section(Entry): for entry in self._entries.values(): entry.AddMissingProperties(have_image_pos) - def ObtainContents(self): - return self.GetEntryContents() + def ObtainContents(self, skip_entry=None): + return self.GetEntryContents(skip_entry=skip_entry) def GetPaddedDataForEntry(self, entry, entry_data): """Get the data for an entry including any padding @@ -527,12 +527,13 @@ class Entry_section(Entry): return entry return None - def GetEntryContents(self): + def GetEntryContents(self, skip_entry=None): """Call ObtainContents() for each entry in the section """ def _CheckDone(entry): - if not entry.ObtainContents(): - next_todo.append(entry) + if entry != skip_entry: + if not entry.ObtainContents(): + next_todo.append(entry) return entry todo = self._entries.values() @@ -620,7 +621,7 @@ class Entry_section(Entry): def ListEntries(self, entries, indent): """List the files in the section""" - Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size, + Entry.AddEntryInfo(entries, indent, self.name, self.etype, self.size, self.image_pos, None, self.offset, self) for entry in self._entries.values(): entry.ListEntries(entries, indent + 1) -- cgit v1.2.3 From 3f495f18a756c786d39c487061df37ea4b5e1ecd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:49 -0700 Subject: binman: Update the section documentation Expand this to explain subclassing better and also to tidy up formatting for rST. Fix a few pylint warnings to avoid dropping the score. Signed-off-by: Simon Glass --- tools/binman/entries.rst | 149 ++++++++++++++++++++++++++++++++++-------- tools/binman/etype/section.py | 148 +++++++++++++++++++++++++++++++++-------- 2 files changed, 242 insertions(+), 55 deletions(-) (limited to 'tools') diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index dcac700c461..748277c1cde 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -799,39 +799,130 @@ This entry holds firmware for an external platform-specific coprocessor. Entry: section: Entry that contains other entries ------------------------------------------------- -Properties / Entry arguments: (see binman README for more information): - pad-byte: Pad byte to use when padding - sort-by-offset: True if entries should be sorted by offset, False if - they must be in-order in the device tree description - - end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32) - - skip-at-start: Number of bytes before the first entry starts. These - effectively adjust the starting offset of entries. For example, - if this is 16, then the first entry would start at 16. An entry - with offset = 20 would in fact be written at offset 4 in the image - file, since the first 16 bytes are skipped when writing. - name-prefix: Adds a prefix to the name of every entry in the section - when writing out the map - align_default: Default alignment for this section, if no alignment is - given in the entry - -Properties: - allow_missing: True if this section permits external blobs to be - missing their contents. The second will produce an image but of - course it will not work. - -Properties: - _allow_missing: True if this section permits external blobs to be - missing their contents. The second will produce an image but of - course it will not work. +A section is an entry which can contain other entries, thus allowing +hierarchical images to be created. See 'Sections and hierarchical images' +in the binman README for more information. + +The base implementation simply joins the various entries together, using +various rules about alignment, etc. + +Subclassing +~~~~~~~~~~~ + +This class can be subclassed to support other file formats which hold +multiple entries, such as CBFS. To do this, override the following +functions. The documentation here describes what your function should do. +For example code, see etypes which subclass `Entry_section`, or `cbfs.py` +for a more involved example:: + + $ grep -l \(Entry_section tools/binman/etype/*.py + +ReadNode() + Call `super().ReadNode()`, then read any special properties for the + section. Then call `self.ReadEntries()` to read the entries. + + Binman calls this at the start when reading the image description. + +ReadEntries() + Read in the subnodes of the section. This may involve creating entries + of a particular etype automatically, as well as reading any special + properties in the entries. For each entry, entry.ReadNode() should be + called, to read the basic entry properties. The properties should be + added to `self._entries[]`, in the correct order, with a suitable name. + + Binman calls this at the start when reading the image description. + +BuildSectionData(required) + Create the custom file format that you want and return it as bytes. + This likely sets up a file header, then loops through the entries, + adding them to the file. For each entry, call `entry.GetData()` to + obtain the data. If that returns None, and `required` is False, then + this method must give up and return None. But if `required` is True then + it should assume that all data is valid. + + Binman calls this when packing the image, to find out the size of + everything. It is called again at the end when building the final image. + +SetImagePos(image_pos): + Call `super().SetImagePos(image_pos)`, then set the `image_pos` values + for each of the entries. This should use the custom file format to find + the `start offset` (and `image_pos`) of each entry. If the file format + uses compression in such a way that there is no offset available (other + than reading the whole file and decompressing it), then the offsets for + affected entries can remain unset (`None`). The size should also be set + if possible. + + Binman calls this after the image has been packed, to update the + location that all the entries ended up at. + +ReadChildData(child, decomp): + The default version of this may be good enough, if you are able to + implement SetImagePos() correctly. But that is a bit of a bypass, so + you can override this method to read from your custom file format. It + should read the entire entry containing the custom file using + `super().ReadData(True)`, then parse the file to get the data for the + given child, then return that data. + + If your file format supports compression, the `decomp` argument tells + you whether to return the compressed data (`decomp` is False) or to + uncompress it first, then return the uncompressed data (`decomp` is + True). This is used by the `binman extract -U` option. + + Binman calls this when reading in an image, in order to populate all the + entries with the data from that image (`binman ls`). + +WriteChildData(child): + Binman calls this after `child.data` is updated, to inform the custom + file format about this, in case it needs to do updates. + + The default version of this does nothing and probably needs to be + overridden for the 'binman replace' command to work. Your version should + use `child.data` to update the data for that child in the custom file + format. + + Binman calls this when updating an image that has been read in and in + particular to update the data for a particular entry (`binman replace`) + +Properties / Entry arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See :ref:`develop/package/binman:Image description format` for more +information. + +align-default + Default alignment for this section, if no alignment is given in the + entry + +pad-byte + Pad byte to use when padding + +sort-by-offset + True if entries should be sorted by offset, False if they must be + in-order in the device tree description + +end-at-4gb + Used to build an x86 ROM which ends at 4GB (2^32) + +name-prefix + Adds a prefix to the name of every entry in the section when writing out + the map + +skip-at-start + Number of bytes before the first entry starts. These effectively adjust + the starting offset of entries. For example, if this is 16, then the + first entry would start at 16. An entry with offset = 20 would in fact + be written at offset 4 in the image file, since the first 16 bytes are + skipped when writing. Since a section is also an entry, it inherits all the properies of entries too. -A section is an entry which can contain other entries, thus allowing -hierarchical images to be created. See 'Sections and hierarchical images' -in the binman README for more information. +Note that the `allow_missing` member controls whether this section permits +external blobs to be missing their contents. The option will produce an +image but of course it will not work. It is useful to make sure that +Continuous Integration systems can build without the binaries being +available. This is set by the `SetAllowMissing()` method, if +`--allow-missing` is passed to binman. diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 76e5eb19648..2e01dccc6db 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -24,34 +24,130 @@ from patman.tools import ToHexSize class Entry_section(Entry): """Entry that contains other entries - Properties / Entry arguments: (see binman README for more information): - pad-byte: Pad byte to use when padding - sort-by-offset: True if entries should be sorted by offset, False if - they must be in-order in the device tree description - - end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32) - - skip-at-start: Number of bytes before the first entry starts. These - effectively adjust the starting offset of entries. For example, - if this is 16, then the first entry would start at 16. An entry - with offset = 20 would in fact be written at offset 4 in the image - file, since the first 16 bytes are skipped when writing. - name-prefix: Adds a prefix to the name of every entry in the section - when writing out the map - align_default: Default alignment for this section, if no alignment is - given in the entry - - Properties: - allow_missing: True if this section permits external blobs to be - missing their contents. The second will produce an image but of - course it will not work. + A section is an entry which can contain other entries, thus allowing + hierarchical images to be created. See 'Sections and hierarchical images' + in the binman README for more information. + + The base implementation simply joins the various entries together, using + various rules about alignment, etc. + + Subclassing + ~~~~~~~~~~~ + + This class can be subclassed to support other file formats which hold + multiple entries, such as CBFS. To do this, override the following + functions. The documentation here describes what your function should do. + For example code, see etypes which subclass `Entry_section`, or `cbfs.py` + for a more involved example:: + + $ grep -l \(Entry_section tools/binman/etype/*.py + + ReadNode() + Call `super().ReadNode()`, then read any special properties for the + section. Then call `self.ReadEntries()` to read the entries. + + Binman calls this at the start when reading the image description. + + ReadEntries() + Read in the subnodes of the section. This may involve creating entries + of a particular etype automatically, as well as reading any special + properties in the entries. For each entry, entry.ReadNode() should be + called, to read the basic entry properties. The properties should be + added to `self._entries[]`, in the correct order, with a suitable name. + + Binman calls this at the start when reading the image description. + + BuildSectionData(required) + Create the custom file format that you want and return it as bytes. + This likely sets up a file header, then loops through the entries, + adding them to the file. For each entry, call `entry.GetData()` to + obtain the data. If that returns None, and `required` is False, then + this method must give up and return None. But if `required` is True then + it should assume that all data is valid. + + Binman calls this when packing the image, to find out the size of + everything. It is called again at the end when building the final image. + + SetImagePos(image_pos): + Call `super().SetImagePos(image_pos)`, then set the `image_pos` values + for each of the entries. This should use the custom file format to find + the `start offset` (and `image_pos`) of each entry. If the file format + uses compression in such a way that there is no offset available (other + than reading the whole file and decompressing it), then the offsets for + affected entries can remain unset (`None`). The size should also be set + if possible. + + Binman calls this after the image has been packed, to update the + location that all the entries ended up at. + + ReadChildData(child, decomp): + The default version of this may be good enough, if you are able to + implement SetImagePos() correctly. But that is a bit of a bypass, so + you can override this method to read from your custom file format. It + should read the entire entry containing the custom file using + `super().ReadData(True)`, then parse the file to get the data for the + given child, then return that data. + + If your file format supports compression, the `decomp` argument tells + you whether to return the compressed data (`decomp` is False) or to + uncompress it first, then return the uncompressed data (`decomp` is + True). This is used by the `binman extract -U` option. + + Binman calls this when reading in an image, in order to populate all the + entries with the data from that image (`binman ls`). + + WriteChildData(child): + Binman calls this after `child.data` is updated, to inform the custom + file format about this, in case it needs to do updates. + + The default version of this does nothing and probably needs to be + overridden for the 'binman replace' command to work. Your version should + use `child.data` to update the data for that child in the custom file + format. + + Binman calls this when updating an image that has been read in and in + particular to update the data for a particular entry (`binman replace`) + + Properties / Entry arguments + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + See :ref:`develop/package/binman:Image description format` for more + information. + + align-default + Default alignment for this section, if no alignment is given in the + entry + + pad-byte + Pad byte to use when padding + + sort-by-offset + True if entries should be sorted by offset, False if they must be + in-order in the device tree description + + end-at-4gb + Used to build an x86 ROM which ends at 4GB (2^32) + + name-prefix + Adds a prefix to the name of every entry in the section when writing out + the map + + skip-at-start + Number of bytes before the first entry starts. These effectively adjust + the starting offset of entries. For example, if this is 16, then the + first entry would start at 16. An entry with offset = 20 would in fact + be written at offset 4 in the image file, since the first 16 bytes are + skipped when writing. Since a section is also an entry, it inherits all the properies of entries too. - A section is an entry which can contain other entries, thus allowing - hierarchical images to be created. See 'Sections and hierarchical images' - in the binman README for more information. + Note that the `allow_missing` member controls whether this section permits + external blobs to be missing their contents. The option will produce an + image but of course it will not work. It is useful to make sure that + Continuous Integration systems can build without the binaries being + available. This is set by the `SetAllowMissing()` method, if + `--allow-missing` is passed to binman. """ def __init__(self, section, etype, node, test=False): if not test: @@ -98,9 +194,9 @@ class Entry_section(Entry): """Raises an error for this section Args: - msg: Error message to use in the raise string + msg (str): Error message to use in the raise string Raises: - ValueError() + ValueError: always """ raise ValueError("Section '%s': %s" % (self._node.path, msg)) -- cgit v1.2.3 From 8cb069ab7467cd4b0a1a4f3fa18ed358c9179557 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:50 -0700 Subject: binman: Move cbfs.ObtainContents() down a bit It is easier to understand this file if reading the entries comes before obtaining the contents, since that is the order in which Binman proceeds. Move the function down a bit. Signed-off-by: Simon Glass --- tools/binman/etype/cbfs.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 0a858b8b849..9e04897d71e 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -174,6 +174,21 @@ class Entry_cbfs(Entry): self.ReadEntries() self.reader = None + def ReadEntries(self): + """Read the subnodes to find out what should go in this CBFS""" + for node in self._node.subnodes: + entry = Entry.Create(self, node) + entry.ReadNode() + entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name) + entry._type = fdt_util.GetString(node, 'cbfs-type') + compress = fdt_util.GetString(node, 'cbfs-compress', 'none') + entry._cbfs_offset = fdt_util.GetInt(node, 'cbfs-offset') + entry._cbfs_compress = cbfs_util.find_compress(compress) + if entry._cbfs_compress is None: + self.Raise("Invalid compression in '%s': '%s'" % + (node.name, compress)) + self._cbfs_entries[entry._cbfs_name] = entry + def ObtainContents(self, skip=None): arch = cbfs_util.find_arch(self._cbfs_arg) if arch is None: @@ -204,21 +219,6 @@ class Entry_cbfs(Entry): self.SetContents(data) return True - def ReadEntries(self): - """Read the subnodes to find out what should go in this CBFS""" - for node in self._node.subnodes: - entry = Entry.Create(self, node) - entry.ReadNode() - entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name) - entry._type = fdt_util.GetString(node, 'cbfs-type') - compress = fdt_util.GetString(node, 'cbfs-compress', 'none') - entry._cbfs_offset = fdt_util.GetInt(node, 'cbfs-offset') - entry._cbfs_compress = cbfs_util.find_compress(compress) - if entry._cbfs_compress is None: - self.Raise("Invalid compression in '%s': '%s'" % - (node.name, compress)) - self._cbfs_entries[entry._cbfs_name] = entry - def SetImagePos(self, image_pos): """Override this function to set all the entry properties from CBFS -- cgit v1.2.3 From 080f859cf180e860ec5f3c8b7940820c5a32b02a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:51 -0700 Subject: binman: Use normal entries in cbfs This currently uses _cbfs_entries[] to store entries. Since the entries are in fact valid etypes, we may as well use the same name as entry_Section uses, which is _entries. This allows reusing more of the code there (in a future patch). Signed-off-by: Simon Glass --- tools/binman/etype/cbfs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 9e04897d71e..873b199a91d 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -170,7 +170,7 @@ class Entry_cbfs(Entry): super().__init__(section, etype, node) self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86') self.align_default = None - self._cbfs_entries = OrderedDict() + self._entries = OrderedDict() self.ReadEntries() self.reader = None @@ -187,7 +187,7 @@ class Entry_cbfs(Entry): if entry._cbfs_compress is None: self.Raise("Invalid compression in '%s': '%s'" % (node.name, compress)) - self._cbfs_entries[entry._cbfs_name] = entry + self._entries[entry._cbfs_name] = entry def ObtainContents(self, skip=None): arch = cbfs_util.find_arch(self._cbfs_arg) @@ -196,7 +196,7 @@ class Entry_cbfs(Entry): if self.size is None: self.Raise("'cbfs' entry must have a size property") cbfs = CbfsWriter(self.size, arch) - for entry in self._cbfs_entries.values(): + for entry in self._entries.values(): # First get the input data and put it in a file. If not available, # try later. if entry != skip and not entry.ObtainContents(): @@ -230,7 +230,7 @@ class Entry_cbfs(Entry): super().SetImagePos(image_pos) # Now update the entries with info from the CBFS entries - for entry in self._cbfs_entries.values(): + for entry in self._entries.values(): cfile = entry._cbfs_file entry.size = cfile.data_len entry.offset = cfile.calced_cbfs_offset @@ -240,7 +240,7 @@ class Entry_cbfs(Entry): def AddMissingProperties(self, have_image_pos): super().AddMissingProperties(have_image_pos) - for entry in self._cbfs_entries.values(): + for entry in self._entries.values(): entry.AddMissingProperties(have_image_pos) if entry._cbfs_compress: state.AddZeroProp(entry._node, 'uncomp-size') @@ -252,7 +252,7 @@ class Entry_cbfs(Entry): def SetCalculatedProperties(self): """Set the value of device-tree properties calculated by binman""" super().SetCalculatedProperties() - for entry in self._cbfs_entries.values(): + for entry in self._entries.values(): state.SetInt(entry._node, 'offset', entry.offset) state.SetInt(entry._node, 'size', entry.size) state.SetInt(entry._node, 'image-pos', entry.image_pos) @@ -262,11 +262,11 @@ class Entry_cbfs(Entry): def ListEntries(self, entries, indent): """Override this method to list all files in the section""" super().ListEntries(entries, indent) - for entry in self._cbfs_entries.values(): + for entry in self._entries.values(): entry.ListEntries(entries, indent + 1) def GetEntries(self): - return self._cbfs_entries + return self._entries def ReadData(self, decomp=True): data = super().ReadData(True) -- cgit v1.2.3 From 3fc20fd8055f59137293e487244d8b3d885bbbe5 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:52 -0700 Subject: binman: cbfs: Refactor the init process Update the constructor to work in the recommended way, where the node properties are read in a separate function. This makes it more similar to entry_Section. Signed-off-by: Simon Glass --- tools/binman/etype/cbfs.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 873b199a91d..a5120127059 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -168,12 +168,16 @@ class Entry_cbfs(Entry): from binman import state super().__init__(section, etype, node) - self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86') self.align_default = None self._entries = OrderedDict() - self.ReadEntries() self.reader = None + def ReadNode(self): + """Read properties from the atf-fip node""" + super().ReadNode() + self._cbfs_arg = fdt_util.GetString(self._node, 'cbfs-arch', 'x86') + self.ReadEntries() + def ReadEntries(self): """Read the subnodes to find out what should go in this CBFS""" for node in self._node.subnodes: -- cgit v1.2.3 From 7413321a47483f1be94dad2e8e3e20c7230ad6e2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:53 -0700 Subject: binman: cfbs: Refactor ObtainContents() for consistency Update this to use the same arguments as entry_Section uses. Signed-off-by: Simon Glass --- tools/binman/etype/cbfs.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index a5120127059..2459388f842 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -193,7 +193,24 @@ class Entry_cbfs(Entry): (node.name, compress)) self._entries[entry._cbfs_name] = entry - def ObtainContents(self, skip=None): + def ObtainCfile(self, cbfs, entry): + # First get the input data and put it in a file. If not available, + # try later. + data = entry.GetData() + cfile = None + if entry._type == 'raw': + cfile = cbfs.add_file_raw(entry._cbfs_name, data, + entry._cbfs_offset, + entry._cbfs_compress) + elif entry._type == 'stage': + cfile = cbfs.add_file_stage(entry._cbfs_name, data, + entry._cbfs_offset) + else: + entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" % + entry._type) + return cfile + + def ObtainContents(self, skip_entry=None): arch = cbfs_util.find_arch(self._cbfs_arg) if arch is None: self.Raise("Invalid architecture '%s'" % self._cbfs_arg) @@ -201,22 +218,9 @@ class Entry_cbfs(Entry): self.Raise("'cbfs' entry must have a size property") cbfs = CbfsWriter(self.size, arch) for entry in self._entries.values(): - # First get the input data and put it in a file. If not available, - # try later. - if entry != skip and not entry.ObtainContents(): + if entry != skip_entry and not entry.ObtainContents(): return False - data = entry.GetData() - cfile = None - if entry._type == 'raw': - cfile = cbfs.add_file_raw(entry._cbfs_name, data, - entry._cbfs_offset, - entry._cbfs_compress) - elif entry._type == 'stage': - cfile = cbfs.add_file_stage(entry._cbfs_name, data, - entry._cbfs_offset) - else: - entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" % - entry._type) + cfile = self.ObtainCfile(cbfs, entry) if cfile: entry._cbfs_file = cfile data = cbfs.get_data() @@ -285,5 +289,7 @@ class Entry_cbfs(Entry): return cfile.data if decomp else cfile.orig_data def WriteChildData(self, child): - self.ObtainContents(skip=child) + # Recreate the data structure, leaving the data for this child alone, + # so that child.data is used to pack into the FIP. + self.ObtainContents(skip_entry=child) return True -- cgit v1.2.3 From e2f0474b05e9667f7f764138c833331fa701e7bf Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 11:03:54 -0700 Subject: binman: Rename testCbfsNoCOntents() Use a lower-case O as was intended. Signed-off-by: Simon Glass --- tools/binman/ftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 3982560c47c..0f4330b6807 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -2251,7 +2251,7 @@ class TestFunctional(unittest.TestCase): self._DoReadFile('107_cbfs_no_size.dts') self.assertIn('entry must have a size property', str(e.exception)) - def testCbfsNoCOntents(self): + def testCbfsNoContents(self): """Test handling of a CBFS entry which does not provide contentsy""" with self.assertRaises(ValueError) as e: self._DoReadFile('108_cbfs_no_contents.dts') -- cgit v1.2.3 From 7945077f7934fff2b9a5fba2860fe330e86093f1 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:48 -0700 Subject: binman: Allow providing tools and blob directories At present it is necessary to symlink files containing external blobs into the U-Boot tree in order for binman to find them. This is not very convenient. Add two new environment/Makefile variables to help with this. Add documentation as well, fixing a related nit. Signed-off-by: Simon Glass --- tools/binman/binman.rst | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 35de93bd898..210d0c5c51b 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -942,7 +942,7 @@ Replacing files in an image --------------------------- You can replace files in an existing firmware image created by binman, provided -that there is an 'fdtmap' entry in the image. For example: +that there is an 'fdtmap' entry in the image. For example:: $ binman replace -i image.bin section/cbfs/u-boot @@ -1081,6 +1081,35 @@ the tool's output will be used for the target or for the host machine. If those aren't given, it will also try to derive target-specific versions from the CROSS_COMPILE environment variable during a cross-compilation. +If the tool is not available in the path you can use BINMAN_TOOLPATHS to specify +a space-separated list of paths to search, e.g.:: + + BINMAN_TOOLPATHS="/tools/g12a /tools/tegra" binman ... + + +External blobs +-------------- + +Binary blobs, even if the source code is available, complicate building +firmware. The instructions can involve multiple steps and the binaries may be +hard to build or obtain. Binman at least provides a unified description of how +to build the final image, no matter what steps are needed to get there. + +Binman also provides a `blob-ext` entry type that pulls in a binary blob from an +external file. If the file is missing, binman can optionally complete the build +and just report a warning. Use the `-M/--allow-missing` option to enble this. +This is useful in CI systems which want to check that everything is correct but +don't have access to the blobs. + +If the blobs are in a different directory, you can specify this with the `-I` +option. + +For U-Boot, you can use set the BINMAN_INDIRS environment variable to provide a +space-separated list of directories to search for binary blobs:: + + BINMAN_INDIRS="odroid-c4/fip/g12a \ + odroid-c4/build/board/hardkernel/odroidc4/firmware \ + odroid-c4/build/scp_task" binman ... Code coverage ------------- -- cgit v1.2.3 From 858436dfda11158ea4bb9e17195dba7f62b30b74 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:49 -0700 Subject: binman: Allow listing an image created by a newer version If an older version of binman is used to list images created by a newer one, it is possible that it will contain entry types that are not supported. At present this produces an error. Adjust binman to use a plain 'blob' entry type to cope with this, so the image can at least be listed. Signed-off-by: Simon Glass --- tools/binman/binman.rst | 5 ++++ tools/binman/entry.py | 65 +++++++++++++++++++++++++++++++++---------- tools/binman/entry_test.py | 9 ++++++ tools/binman/etype/section.py | 3 +- tools/binman/image.py | 10 +++++-- 5 files changed, 74 insertions(+), 18 deletions(-) (limited to 'tools') diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 210d0c5c51b..26f462ae16f 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -913,6 +913,11 @@ or with wildcards:: u-boot-dtb 180 108 u-boot-dtb 80 3b5 image-header bf8 8 image-header bf8 +If an older version of binman is used to list images created by a newer one, it +is possible that it will contain entry types that are not supported. These still +show with the correct type, but binman just sees them as blobs (plain binary +data). Any special features of that etype are not supported by the old binman. + Extracting files from images ---------------------------- diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 2205bc8d923..e7a8365fd51 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -102,7 +102,7 @@ class Entry(object): self.allow_missing = False @staticmethod - def Lookup(node_path, etype, expanded): + def FindEntryClass(etype, expanded): """Look up the entry class for a node. Args: @@ -113,10 +113,9 @@ class Entry(object): Returns: The entry class object if found, else None if not found and expanded - is True - - Raise: - ValueError if expanded is False and the class is not found + is True, else a tuple: + module name that could not be found + exception received """ # Convert something like 'u-boot@0' to 'u_boot' since we are only # interested in the type. @@ -137,30 +136,66 @@ class Entry(object): except ImportError as e: if expanded: return None - raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" % - (etype, node_path, module_name, e)) + return module_name, e modules[module_name] = module # Look up the expected class name return getattr(module, 'Entry_%s' % module_name) @staticmethod - def Create(section, node, etype=None, expanded=False): + def Lookup(node_path, etype, expanded, missing_etype=False): + """Look up the entry class for a node. + + Args: + node_node (str): Path name of Node object containing information + about the entry to create (used for errors) + etype (str): Entry type to use + expanded (bool): Use the expanded version of etype + missing_etype (bool): True to default to a blob etype if the + requested etype is not found + + Returns: + The entry class object if found, else None if not found and expanded + is True + + Raise: + ValueError if expanded is False and the class is not found + """ + # Convert something like 'u-boot@0' to 'u_boot' since we are only + # interested in the type. + cls = Entry.FindEntryClass(etype, expanded) + if cls is None: + return None + elif isinstance(cls, tuple): + if missing_etype: + cls = Entry.FindEntryClass('blob', False) + if isinstance(cls, tuple): # This should not fail + module_name, e = cls + raise ValueError( + "Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" % + (etype, node_path, module_name, e)) + return cls + + @staticmethod + def Create(section, node, etype=None, expanded=False, missing_etype=False): """Create a new entry for a node. Args: - section: Section object containing this node - node: Node object containing information about the entry to - create - etype: Entry type to use, or None to work it out (used for tests) - expanded: True to use expanded versions of entries, where available + section (entry_Section): Section object containing this node + node (Node): Node object containing information about the entry to + create + etype (str): Entry type to use, or None to work it out (used for + tests) + expanded (bool): Use the expanded version of etype + missing_etype (bool): True to default to a blob etype if the + requested etype is not found Returns: A new Entry object of the correct type (a subclass of Entry) """ if not etype: etype = fdt_util.GetString(node, 'type', node.name) - obj = Entry.Lookup(node.path, etype, expanded) + obj = Entry.Lookup(node.path, etype, expanded, missing_etype) if obj and expanded: # Check whether to use the expanded entry new_etype = etype + '-expanded' @@ -170,7 +205,7 @@ class Entry(object): else: obj = None if not obj: - obj = Entry.Lookup(node.path, etype, False) + obj = Entry.Lookup(node.path, etype, False, missing_etype) # Call its constructor to get the object we want. return obj(section, etype, node) diff --git a/tools/binman/entry_test.py b/tools/binman/entry_test.py index c3d5f3eef48..1b59c9056ec 100644 --- a/tools/binman/entry_test.py +++ b/tools/binman/entry_test.py @@ -10,6 +10,7 @@ import sys import unittest from binman import entry +from binman.etype.blob import Entry_blob from dtoc import fdt from dtoc import fdt_util from patman import tools @@ -100,5 +101,13 @@ class TestEntry(unittest.TestCase): self.assertIn("Unknown entry type 'missing' in node '/binman/u-boot'", str(e.exception)) + def testMissingEtype(self): + """Test use of a blob etype when the requested one is not available""" + ent = entry.Entry.Create(None, self.GetNode(), 'missing', + missing_etype=True) + self.assertTrue(isinstance(ent, Entry_blob)) + self.assertEquals('missing', ent.etype) + + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 2e01dccc6db..6ce07dd37d7 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -185,7 +185,8 @@ class Entry_section(Entry): if node.name.startswith('hash') or node.name.startswith('signature'): continue entry = Entry.Create(self, node, - expanded=self.GetImage().use_expanded) + expanded=self.GetImage().use_expanded, + missing_etype=self.GetImage().missing_etype) entry.ReadNode() entry.SetPrefix(self._name_prefix) self._entries[node.name] = entry diff --git a/tools/binman/image.py b/tools/binman/image.py index cdc58b39a40..891e8b488e9 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -63,9 +63,13 @@ class Image(section.Entry_section): to ignore 'u-boot-bin' in this case, and build it ourselves in binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See Entry_u_boot_expanded and Entry_blob_phase for details. + missing_etype: Use a default entry type ('blob') if the requested one + does not exist in binman. This is useful if an image was created by + binman a newer version of binman but we want to list it in an older + version which does not support all the entry types. """ def __init__(self, name, node, copy_to_orig=True, test=False, - ignore_missing=False, use_expanded=False): + ignore_missing=False, use_expanded=False, missing_etype=False): super().__init__(None, 'section', node, test=test) self.copy_to_orig = copy_to_orig self.name = 'main-section' @@ -75,6 +79,7 @@ class Image(section.Entry_section): self.fdtmap_data = None self.allow_repack = False self._ignore_missing = ignore_missing + self.missing_etype = missing_etype self.use_expanded = use_expanded self.test_section_timeout = False if not test: @@ -124,7 +129,8 @@ class Image(section.Entry_section): # Return an Image with the associated nodes root = dtb.GetRoot() - image = Image('image', root, copy_to_orig=False, ignore_missing=True) + image = Image('image', root, copy_to_orig=False, ignore_missing=True, + missing_etype=True) image.image_node = fdt_util.GetString(root, 'image-node', 'image') image.fdtmap_dtb = dtb -- cgit v1.2.3 From 943bf78a48ac22ce0277697976fef4f89457b446 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:50 -0700 Subject: binman: Allow extracting a file in an alternative format In some cases entries encapsulate other data and it is useful to access the data within. An example is the fdtmap which consists of a 16-byte header, followed by a devicetree. Provide an option to specify an alternative format when extracting files. In the case of fdtmap, this is 'fdt', which produces an FDT file which can be viewed with fdtdump. Signed-off-by: Simon Glass --- tools/binman/binman.rst | 29 ++++++++++++++++++++++ tools/binman/cmdline.py | 2 ++ tools/binman/control.py | 28 ++++++++++++++++++---- tools/binman/entries.rst | 11 ++++++++- tools/binman/entry.py | 37 ++++++++++++++++++++++++----- tools/binman/etype/cbfs.py | 8 +++---- tools/binman/etype/fdtmap.py | 12 ++++++++++ tools/binman/etype/section.py | 23 ++++++++++++++---- tools/binman/ftest.py | 34 ++++++++++++++++++++++++++ tools/binman/image.py | 2 +- tools/binman/test/213_fdtmap_alt_format.dts | 15 ++++++++++++ tools/binman/test/214_no_alt_format.dts | 13 ++++++++++ 12 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 tools/binman/test/213_fdtmap_alt_format.dts create mode 100644 tools/binman/test/214_no_alt_format.dts (limited to 'tools') diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 26f462ae16f..10389a52c4b 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -942,6 +942,35 @@ or just a selection:: $ binman extract -i image.bin "*u-boot*" -O outdir +Some entry types have alternative formats, for example fdtmap which allows +extracted just the devicetree binary without the fdtmap header:: + + $ binman extract -i /tmp/b/odroid-c4/image.bin -f out.dtb -F fdt fdtmap + $ fdtdump out.dtb + /dts-v1/; + // magic: 0xd00dfeed + // totalsize: 0x8ab (2219) + // off_dt_struct: 0x38 + // off_dt_strings: 0x82c + // off_mem_rsvmap: 0x28 + // version: 17 + // last_comp_version: 2 + // boot_cpuid_phys: 0x0 + // size_dt_strings: 0x7f + // size_dt_struct: 0x7f4 + + / { + image-node = "binman"; + image-pos = <0x00000000>; + size = <0x0011162b>; + ... + +Use `-F list` to see what alternative formats are available:: + + $ binman extract -i /tmp/b/odroid-c4/image.bin -F list + Flag (-F) Entry type Description + fdt fdtmap Extract the devicetree blob from the fdtmap + Replacing files in an image --------------------------- diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py index 2229316f10e..adc17547ae6 100644 --- a/tools/binman/cmdline.py +++ b/tools/binman/cmdline.py @@ -17,6 +17,8 @@ def make_extract_parser(subparsers): """ extract_parser = subparsers.add_parser('extract', help='Extract files from an image') + extract_parser.add_argument('-F', '--format', type=str, + help='Select an alternative format for extracted data') extract_parser.add_argument('-i', '--image', type=str, required=True, help='Image filename to extract') extract_parser.add_argument('-f', '--filename', type=str, diff --git a/tools/binman/control.py b/tools/binman/control.py index 7da69ba38de..dcf070da85f 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -200,8 +200,24 @@ def ReadEntry(image_fname, entry_path, decomp=True): return entry.ReadData(decomp) +def ShowAltFormats(image): + """Show alternative formats available for entries in the image + + This shows a list of formats available. + + Args: + image (Image): Image to check + """ + alt_formats = {} + image.CheckAltFormats(alt_formats) + print('%-10s %-20s %s' % ('Flag (-F)', 'Entry type', 'Description')) + for name, val in alt_formats.items(): + entry, helptext = val + print('%-10s %-20s %s' % (name, entry.etype, helptext)) + + def ExtractEntries(image_fname, output_fname, outdir, entry_paths, - decomp=True): + decomp=True, alt_format=None): """Extract the data from one or more entries and write it to files Args: @@ -217,6 +233,10 @@ def ExtractEntries(image_fname, output_fname, outdir, entry_paths, """ image = Image.FromFile(image_fname) + if alt_format == 'list': + ShowAltFormats(image) + return + # Output an entry to a single file, as a special case if output_fname: if not entry_paths: @@ -224,7 +244,7 @@ def ExtractEntries(image_fname, output_fname, outdir, entry_paths, if len(entry_paths) != 1: raise ValueError('Must specify exactly one entry path to write with -f') entry = image.FindEntryPath(entry_paths[0]) - data = entry.ReadData(decomp) + data = entry.ReadData(decomp, alt_format) tools.WriteFile(output_fname, data) tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname)) return @@ -236,7 +256,7 @@ def ExtractEntries(image_fname, output_fname, outdir, entry_paths, tout.Notice('%d entries match and will be written' % len(einfos)) for einfo in einfos: entry = einfo.entry - data = entry.ReadData(decomp) + data = entry.ReadData(decomp, alt_format) path = entry.GetPath()[1:] fname = os.path.join(outdir, path) @@ -584,7 +604,7 @@ def Binman(args): if args.cmd == 'extract': ExtractEntries(args.image, args.filename, args.outdir, args.paths, - not args.uncompressed) + not args.uncompressed, args.format) if args.cmd == 'replace': ReplaceEntries(args.image, args.filename, args.indir, args.paths, diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index 748277c1cde..2ebac517ce6 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -314,6 +314,10 @@ Example output for a simple image with U-Boot and an FDT map:: If allow-repack is used then 'orig-offset' and 'orig-size' properties are added as necessary. See the binman README. +When extracting files, an alternative 'fdt' format is available for fdtmaps. +Use `binman extract -F fdt ...` to use this. It will export a devicetree, +without the fdtmap header, so it can be viewed with `fdtdump`. + Entry: files: A set of files arranged in a section @@ -855,7 +859,7 @@ SetImagePos(image_pos): Binman calls this after the image has been packed, to update the location that all the entries ended up at. -ReadChildData(child, decomp): +ReadChildData(child, decomp, alt_format): The default version of this may be good enough, if you are able to implement SetImagePos() correctly. But that is a bit of a bypass, so you can override this method to read from your custom file format. It @@ -868,6 +872,11 @@ ReadChildData(child, decomp): uncompress it first, then return the uncompressed data (`decomp` is True). This is used by the `binman extract -U` option. + If your entry supports alternative formats, the alt_format provides the + alternative format that the user has selected. Your function should + return data in that format. This is used by the 'binman extract -l' + option. + Binman calls this when reading in an image, in order to populate all the entries with the data from that image (`binman ls`). diff --git a/tools/binman/entry.py b/tools/binman/entry.py index e7a8365fd51..61642bf5017 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -815,7 +815,7 @@ features to produce new behaviours. self.AddEntryInfo(entries, indent, self.name, self.etype, self.size, self.image_pos, self.uncomp_size, self.offset, self) - def ReadData(self, decomp=True): + def ReadData(self, decomp=True, alt_format=None): """Read the data for an entry from the image This is used when the image has been read in and we want to extract the @@ -832,19 +832,20 @@ features to produce new behaviours. # although compressed sections are currently not supported tout.Debug("ReadChildData section '%s', entry '%s'" % (self.section.GetPath(), self.GetPath())) - data = self.section.ReadChildData(self, decomp) + data = self.section.ReadChildData(self, decomp, alt_format) return data - def ReadChildData(self, child, decomp=True): + def ReadChildData(self, child, decomp=True, alt_format=None): """Read the data for a particular child entry This reads data from the parent and extracts the piece that relates to the given child. Args: - child: Child entry to read data for (must be valid) - decomp: True to decompress any compressed data before returning it; - False to return the raw, uncompressed data + child (Entry): Child entry to read data for (must be valid) + decomp (bool): True to decompress any compressed data before + returning it; False to return the raw, uncompressed data + alt_format (str): Alternative format to read in, or None Returns: Data for the child (bytes) @@ -857,6 +858,20 @@ features to produce new behaviours. self.ProcessContentsUpdate(data) self.Detail('Loaded data size %x' % len(data)) + def GetAltFormat(self, data, alt_format): + """Read the data for an extry in an alternative format + + Supported formats are list in the documentation for each entry. An + example is fdtmap which provides . + + Args: + data (bytes): Data to convert (this should have been produced by the + entry) + alt_format (str): Format to use + + """ + pass + def GetImage(self): """Get the image containing this entry @@ -997,3 +1012,13 @@ features to produce new behaviours. tout.Info("Node '%s': etype '%s': %s selected" % (node.path, etype, new_etype)) return True + + def CheckAltFormats(self, alt_formats): + """Add any alternative formats supported by this entry type + + Args: + alt_formats (dict): Dict to add alt_formats to: + key: Name of alt format + value: Help text + """ + pass diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 2459388f842..cc1fbdf4b57 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -276,13 +276,13 @@ class Entry_cbfs(Entry): def GetEntries(self): return self._entries - def ReadData(self, decomp=True): - data = super().ReadData(True) + def ReadData(self, decomp=True, alt_format=None): + data = super().ReadData(True, alt_format) return data - def ReadChildData(self, child, decomp=True): + def ReadChildData(self, child, decomp=True, alt_format=None): if not self.reader: - data = super().ReadData(True) + data = super().ReadData(True, alt_format) self.reader = cbfs_util.CbfsReader(data) reader = self.reader cfile = reader.files.get(child.name) diff --git a/tools/binman/etype/fdtmap.py b/tools/binman/etype/fdtmap.py index 2339feeba8d..aaaf2de4383 100644 --- a/tools/binman/etype/fdtmap.py +++ b/tools/binman/etype/fdtmap.py @@ -74,6 +74,10 @@ class Entry_fdtmap(Entry): If allow-repack is used then 'orig-offset' and 'orig-size' properties are added as necessary. See the binman README. + + When extracting files, an alternative 'fdt' format is available for fdtmaps. + Use `binman extract -F fdt ...` to use this. It will export a devicetree, + without the fdtmap header, so it can be viewed with `fdtdump`. """ def __init__(self, section, etype, node): # Put these here to allow entry-docs and help to work without libfdt @@ -86,6 +90,10 @@ class Entry_fdtmap(Entry): from dtoc.fdt import Fdt super().__init__(section, etype, node) + self.alt_formats = ['fdt'] + + def CheckAltFormats(self, alt_formats): + alt_formats['fdt'] = self, 'Extract the devicetree blob from the fdtmap' def _GetFdtmap(self): """Build an FDT map from the entries in the current image @@ -147,3 +155,7 @@ class Entry_fdtmap(Entry): processing, e.g. the image-pos properties. """ return self.ProcessContentsUpdate(self._GetFdtmap()) + + def GetAltFormat(self, data, alt_format): + if alt_format == 'fdt': + return data[FDTMAP_HDR_LEN:] diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 6ce07dd37d7..43436a11f27 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -80,7 +80,7 @@ class Entry_section(Entry): Binman calls this after the image has been packed, to update the location that all the entries ended up at. - ReadChildData(child, decomp): + ReadChildData(child, decomp, alt_format): The default version of this may be good enough, if you are able to implement SetImagePos() correctly. But that is a bit of a bypass, so you can override this method to read from your custom file format. It @@ -93,6 +93,11 @@ class Entry_section(Entry): uncompress it first, then return the uncompressed data (`decomp` is True). This is used by the `binman extract -U` option. + If your entry supports alternative formats, the alt_format provides the + alternative format that the user has selected. Your function should + return data in that format. This is used by the 'binman extract -l' + option. + Binman calls this when reading in an image, in order to populate all the entries with the data from that image (`binman ls`). @@ -750,9 +755,9 @@ class Entry_section(Entry): """ return self._sort - def ReadData(self, decomp=True): + def ReadData(self, decomp=True, alt_format=None): tout.Info("ReadData path='%s'" % self.GetPath()) - parent_data = self.section.ReadData(True) + parent_data = self.section.ReadData(True, alt_format) offset = self.offset - self.section._skip_at_start data = parent_data[offset:offset + self.size] tout.Info( @@ -761,9 +766,9 @@ class Entry_section(Entry): self.size, len(data))) return data - def ReadChildData(self, child, decomp=True): + def ReadChildData(self, child, decomp=True, alt_format=None): tout.Debug(f"ReadChildData for child '{child.GetPath()}'") - parent_data = self.ReadData(True) + parent_data = self.ReadData(True, alt_format) offset = child.offset - self._skip_at_start tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" % (child.GetPath(), child.offset, self._skip_at_start, offset)) @@ -775,6 +780,10 @@ class Entry_section(Entry): tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" % (child.GetPath(), len(indata), child.compress, len(data))) + if alt_format: + new_data = child.GetAltFormat(data, alt_format) + if new_data is not None: + data = new_data return data def WriteChildData(self, child): @@ -846,3 +855,7 @@ class Entry_section(Entry): if not self._ignore_missing: missing = ', '.join(missing) entry.Raise(f'Missing required properties/entry args: {missing}') + + def CheckAltFormats(self, alt_formats): + for entry in self._entries.values(): + entry.CheckAltFormats(alt_formats) diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 0f4330b6807..d3a6cbf71df 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -4681,6 +4681,40 @@ class TestFunctional(unittest.TestCase): binary=False) self.assertEqual(version, state.GetVersion(self._indir)) + def testAltFormat(self): + """Test that alternative formats can be used to extract""" + self._DoReadFileRealDtb('213_fdtmap_alt_format.dts') + + try: + tmpdir, updated_fname = self._SetupImageInTmpdir() + with test_util.capture_sys_output() as (stdout, _): + self._DoBinman('extract', '-i', updated_fname, '-F', 'list') + self.assertEqual( + '''Flag (-F) Entry type Description +fdt fdtmap Extract the devicetree blob from the fdtmap +''', + stdout.getvalue()) + + dtb = os.path.join(tmpdir, 'fdt.dtb') + self._DoBinman('extract', '-i', updated_fname, '-F', 'fdt', '-f', + dtb, 'fdtmap') + + # Check that we can read it and it can be scanning, meaning it does + # not have a 16-byte fdtmap header + data = tools.ReadFile(dtb) + dtb = fdt.Fdt.FromData(data) + dtb.Scan() + + # Now check u-boot which has no alt_format + fname = os.path.join(tmpdir, 'fdt.dtb') + self._DoBinman('extract', '-i', updated_fname, '-F', 'dummy', + '-f', fname, 'u-boot') + data = tools.ReadFile(fname) + self.assertEqual(U_BOOT_DATA, data) + + finally: + shutil.rmtree(tmpdir) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/image.py b/tools/binman/image.py index 891e8b488e9..f0a7d65299e 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -223,7 +223,7 @@ class Image(section.Entry_section): entries = entry.GetEntries() return entry - def ReadData(self, decomp=True): + def ReadData(self, decomp=True, alt_format=None): tout.Debug("Image '%s' ReadData(), size=%#x" % (self.GetPath(), len(self._data))) return self._data diff --git a/tools/binman/test/213_fdtmap_alt_format.dts b/tools/binman/test/213_fdtmap_alt_format.dts new file mode 100644 index 00000000000..d9aef04bcf6 --- /dev/null +++ b/tools/binman/test/213_fdtmap_alt_format.dts @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fdtmap { + }; + }; +}; diff --git a/tools/binman/test/214_no_alt_format.dts b/tools/binman/test/214_no_alt_format.dts new file mode 100644 index 00000000000..f00bcdd5764 --- /dev/null +++ b/tools/binman/test/214_no_alt_format.dts @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + }; +}; -- cgit v1.2.3 From 1b5a5331f3c7ad3ae5688841a7a6e710a2cb4dc7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:51 -0700 Subject: dtoc: Add support for reading string-list properties Add a function to read a list of strings from the devicetree. Signed-off-by: Simon Glass --- tools/dtoc/fdt_util.py | 21 +++++++++++++++++++++ tools/dtoc/test_fdt.py | 9 +++++++++ 2 files changed, 30 insertions(+) (limited to 'tools') diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py index 51bdbdcd3b2..19eb13aef33 100644 --- a/tools/dtoc/fdt_util.py +++ b/tools/dtoc/fdt_util.py @@ -163,6 +163,27 @@ def GetString(node, propname, default=None): "a single string" % (node.name, propname)) return value +def GetStringList(node, propname, default=None): + """Get a string list from a property + + Args: + node (Node): Node object to read from + propname (str): property name to read + default (list of str): Default value to use if the node/property do not + exist, or None + + Returns: + String value read, or default if none + """ + prop = node.props.get(propname) + if not prop: + return default + value = prop.value + if not isinstance(value, list): + strval = GetString(node, propname) + return [strval] + return value + def GetBool(node, propname, default=False): """Get an boolean from a property diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 7a4c7efaa4a..55b70e98764 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -615,6 +615,15 @@ class TestFdtUtil(unittest.TestCase): self.assertIn("property 'stringarray' has list value: expecting a " 'single string', str(e.exception)) + def testGetStringList(self): + self.assertEqual(['message'], + fdt_util.GetStringList(self.node, 'stringval')) + self.assertEqual( + ['multi-word', 'message'], + fdt_util.GetStringList(self.node, 'stringarray')) + self.assertEqual(['test'], + fdt_util.GetStringList(self.node, 'missing', ['test'])) + def testGetBool(self): self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) -- cgit v1.2.3 From cc2c50042690151b1b31d9b6d0f1a9dc5831ee5f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:52 -0700 Subject: binman: Support lists of external blobs Sometimes it is useful to have a list of related external blobs in a single entry. An example is the DDR binaries used by meson. There are 9 files in total. Add support for this, so we don't have to have a separate entry for each. Signed-off-by: Simon Glass --- tools/binman/entries.rst | 14 ++++++ tools/binman/etype/blob.py | 16 +++++-- tools/binman/etype/blob_ext_list.py | 58 +++++++++++++++++++++++++ tools/binman/ftest.py | 20 +++++++++ tools/binman/test/215_blob_ext_list.dts | 14 ++++++ tools/binman/test/216_blob_ext_list_missing.dts | 14 ++++++ 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 tools/binman/etype/blob_ext_list.py create mode 100644 tools/binman/test/215_blob_ext_list.dts create mode 100644 tools/binman/test/216_blob_ext_list_missing.dts (limited to 'tools') diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index 2ebac517ce6..d5aa3b0f4a4 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -69,6 +69,20 @@ See 'blob' for Properties / Entry arguments. +Entry: blob-ext-list: List of externally built binary blobs +----------------------------------------------------------- + +This is like blob-ext except that a number of blobs can be provided, +typically with some sort of relationship, e.g. all are DDC parameters. + +If any of the external files needed by this llist is missing, binman can +optionally ignore it and produce a broken image with a warning. + +Args: + filenames: List of filenames to read and include + + + Entry: blob-named-by-arg: A blob entry which gets its filename property from its subclass ----------------------------------------------------------------------------------------- diff --git a/tools/binman/etype/blob.py b/tools/binman/etype/blob.py index fae86ca3ec0..8c1b809e8da 100644 --- a/tools/binman/etype/blob.py +++ b/tools/binman/etype/blob.py @@ -48,10 +48,10 @@ class Entry_blob(Entry): self.ReadBlobContents() return True - def ReadBlobContents(self): + def ReadFileContents(self, pathname): """Read blob contents into memory - This function compresses the data before storing if needed. + This function compresses the data before returning if needed. We assume the data is small enough to fit into memory. If this is used for large filesystem image that might not be true. @@ -59,13 +59,23 @@ class Entry_blob(Entry): new Entry method which can read in chunks. Then we could copy the data in chunks and avoid reading it all at once. For now this seems like an unnecessary complication. + + Args: + pathname (str): Pathname to read from + + Returns: + bytes: Data read """ state.TimingStart('read') - indata = tools.ReadFile(self._pathname) + indata = tools.ReadFile(pathname) state.TimingAccum('read') state.TimingStart('compress') data = self.CompressData(indata) state.TimingAccum('compress') + return data + + def ReadBlobContents(self): + data = self.ReadFileContents(self._pathname) self.SetContents(data) return True diff --git a/tools/binman/etype/blob_ext_list.py b/tools/binman/etype/blob_ext_list.py new file mode 100644 index 00000000000..136ae819946 --- /dev/null +++ b/tools/binman/etype/blob_ext_list.py @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2016 Google, Inc +# Written by Simon Glass +# +# Entry-type module for a list of external blobs, not built by U-Boot +# + +import os + +from binman.etype.blob import Entry_blob +from dtoc import fdt_util +from patman import tools +from patman import tout + +class Entry_blob_ext_list(Entry_blob): + """List of externally built binary blobs + + This is like blob-ext except that a number of blobs can be provided, + typically with some sort of relationship, e.g. all are DDC parameters. + + If any of the external files needed by this llist is missing, binman can + optionally ignore it and produce a broken image with a warning. + + Args: + filenames: List of filenames to read and include + """ + def __init__(self, section, etype, node): + Entry_blob.__init__(self, section, etype, node) + self.external = True + + def ReadNode(self): + super().ReadNode() + self._filenames = fdt_util.GetStringList(self._node, 'filenames') + self._pathnames = [] + + def ObtainContents(self): + missing = False + pathnames = [] + for fname in self._filenames: + pathname = tools.GetInputFilename( + fname, self.external and self.section.GetAllowMissing()) + # Allow the file to be missing + if not pathname: + missing = True + pathnames.append(pathname) + self._pathnames = pathnames + + if missing: + self.SetContents(b'') + self.missing = True + return True + + data = bytearray() + for pathname in pathnames: + data += self.ReadFileContents(pathname) + + self.SetContents(data) + return True diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index d3a6cbf71df..f5ceb9fb17d 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -4715,6 +4715,26 @@ fdt fdtmap Extract the devicetree blob from the fdtmap finally: shutil.rmtree(tmpdir) + def testExtblobList(self): + """Test an image with an external blob list""" + data = self._DoReadFile('215_blob_ext_list.dts') + self.assertEqual(REFCODE_DATA + FSP_M_DATA, data) + + def testExtblobListMissing(self): + """Test an image with a missing external blob""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('216_blob_ext_list_missing.dts') + self.assertIn("Filename 'missing-file' not found in input path", + str(e.exception)) + + def testExtblobListMissingOk(self): + """Test an image with an missing external blob that is allowed""" + with test_util.capture_sys_output() as (stdout, stderr): + self._DoTestFile('216_blob_ext_list_missing.dts', + allow_missing=True) + err = stderr.getvalue() + self.assertRegex(err, "Image 'main-section'.*missing.*: blob-ext") + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/215_blob_ext_list.dts b/tools/binman/test/215_blob_ext_list.dts new file mode 100644 index 00000000000..aad2f0300d3 --- /dev/null +++ b/tools/binman/test/215_blob_ext_list.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + blob-ext-list { + filenames = "refcode.bin", "fsp_m.bin"; + }; + }; +}; diff --git a/tools/binman/test/216_blob_ext_list_missing.dts b/tools/binman/test/216_blob_ext_list_missing.dts new file mode 100644 index 00000000000..c02c335c760 --- /dev/null +++ b/tools/binman/test/216_blob_ext_list_missing.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + blob-ext-list { + filenames = "refcode.bin", "missing-file"; + }; + }; +}; -- cgit v1.2.3 From 5bf81216463dbeb5b5dcdbc22e2f5c8589c333fb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:09:53 -0700 Subject: binman: Rename _ReadSubnodes() to ReadEntries() This method name is more commonly used for this function. Use it consistently. Signed-off-by: Simon Glass --- tools/binman/etype/fit.py | 4 ++-- tools/binman/etype/intel_ifwi.py | 4 ++-- tools/binman/etype/mkimage.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'tools') diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index 6936f5736a6..b41187df80a 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -136,10 +136,10 @@ class Entry_fit(Entry): str)])[0] def ReadNode(self): - self._ReadSubnodes() + self.ReadEntries() super().ReadNode() - def _ReadSubnodes(self): + def ReadEntries(self): def _AddNode(base_node, depth, node): """Add a node to the FIT diff --git a/tools/binman/etype/intel_ifwi.py b/tools/binman/etype/intel_ifwi.py index 903d39bdbeb..ecbd78df5e5 100644 --- a/tools/binman/etype/intel_ifwi.py +++ b/tools/binman/etype/intel_ifwi.py @@ -50,7 +50,7 @@ class Entry_intel_ifwi(Entry_blob_ext): self._ifwi_entries = OrderedDict() def ReadNode(self): - self._ReadSubnodes() + self.ReadEntries() super().ReadNode() def _BuildIfwi(self): @@ -117,7 +117,7 @@ class Entry_intel_ifwi(Entry_blob_ext): same = orig_data == self.data return same - def _ReadSubnodes(self): + def ReadEntries(self): """Read the subnodes to find out what should go in this IFWI""" for node in self._node.subnodes: entry = Entry.Create(self.section, node) diff --git a/tools/binman/etype/mkimage.py b/tools/binman/etype/mkimage.py index e49977522e3..c08fd9dc0b6 100644 --- a/tools/binman/etype/mkimage.py +++ b/tools/binman/etype/mkimage.py @@ -37,7 +37,7 @@ class Entry_mkimage(Entry): self._args = fdt_util.GetString(self._node, 'args').split(' ') self._mkimage_entries = OrderedDict() self.align_default = None - self._ReadSubnodes() + self.ReadEntries() def ObtainContents(self): data = b'' @@ -55,7 +55,7 @@ class Entry_mkimage(Entry): self.SetContents(tools.ReadFile(output_fname)) return True - def _ReadSubnodes(self): + def ReadEntries(self): """Read the subnodes to find out what should go in this image""" for node in self._node.subnodes: entry = Entry.Create(self, node) -- cgit v1.2.3 From ed16b12576ad386d3b22257b7399cd9eb01c69b4 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:08:58 -0700 Subject: binman: Add a utility module for ATF FIP Add support for this format which is used by ARM Trusted Firmware to find firmware binaries to load. FIP is like a simpler version of FMAP but uses a UUID instead of a name, for each entry. It supports reading a FIP, writing a FIP and parsing the ATF source code to get a list of supported UUIDs. Signed-off-by: Simon Glass --- tools/binman/fip_util.py | 653 ++++++++++++++++++++++++++++++++++++++++++ tools/binman/fip_util_test.py | 405 ++++++++++++++++++++++++++ tools/binman/main.py | 4 +- 3 files changed, 1061 insertions(+), 1 deletion(-) create mode 100755 tools/binman/fip_util.py create mode 100755 tools/binman/fip_util_test.py (limited to 'tools') diff --git a/tools/binman/fip_util.py b/tools/binman/fip_util.py new file mode 100755 index 00000000000..5f7dbc04d56 --- /dev/null +++ b/tools/binman/fip_util.py @@ -0,0 +1,653 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2021 Google LLC +# Written by Simon Glass + +"""Support for ARM's Firmware Image Package (FIP) format + +FIP is a format similar to FMAP[1] but with fewer features and an obscure UUID +instead of the region name. + +It consists of a header and a table of entries, each pointing to a place in the +firmware image where something can be found. + +[1] https://chromium.googlesource.com/chromiumos/third_party/flashmap/+/refs/heads/master/lib/fmap.h + +If ATF updates, run this program to update the FIT_TYPE_LIST. + +ARM Trusted Firmware is available at: + +https://github.com/ARM-software/arm-trusted-firmware.git +""" + +from argparse import ArgumentParser +import collections +import io +import os +import re +import struct +import sys +from uuid import UUID + +OUR_FILE = os.path.realpath(__file__) +OUR_PATH = os.path.dirname(OUR_FILE) + +# Bring in the patman and dtoc libraries (but don't override the first path +# in PYTHONPATH) +sys.path.insert(2, os.path.join(OUR_PATH, '..')) + +# pylint: disable=C0413 +from patman import command +from patman import tools + +# The TOC header, at the start of the FIP +HEADER_FORMAT = ' + +"""Tests for fip_util + +This tests a few features of fip_util which are not covered by binman's ftest.py +""" + +import os +import shutil +import sys +import tempfile +import unittest + +# Bring in the patman and dtoc libraries (but don't override the first path +# in PYTHONPATH) +OUR_PATH = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(2, os.path.join(OUR_PATH, '..')) + +# pylint: disable=C0413 +from patman import test_util +from patman import tools +import fip_util + +HAVE_FIPTOOL = True +try: + tools.Run('which', 'fiptool') +except ValueError: + HAVE_FIPTOOL = False + +# pylint: disable=R0902,R0904 +class TestFip(unittest.TestCase): + """Test of fip_util classes""" + #pylint: disable=W0212 + def setUp(self): + # Create a temporary directory for test files + self._indir = tempfile.mkdtemp(prefix='fip_util.') + tools.SetInputDirs([self._indir]) + + # Set up a temporary output directory, used by the tools library when + # compressing files + tools.PrepareOutputDir(None) + + self.src_file = os.path.join(self._indir, 'orig.py') + self.outname = tools.GetOutputFilename('out.py') + self.args = ['-D', '-s', self._indir, '-o', self.outname] + self.readme = os.path.join(self._indir, 'readme.rst') + self.macro_dir = os.path.join(self._indir, 'include/tools_share') + self.macro_fname = os.path.join(self.macro_dir, + 'firmware_image_package.h') + self.name_dir = os.path.join(self._indir, 'tools/fiptool') + self.name_fname = os.path.join(self.name_dir, 'tbbr_config.c') + + macro_contents = ''' + +/* ToC Entry UUIDs */ +#define UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U \\ + {{0x65, 0x92, 0x27, 0x03}, {0x2f, 0x74}, {0xe6, 0x44}, 0x8d, 0xff, {0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10} } +#define UUID_TRUSTED_UPDATE_FIRMWARE_BL2U \\ + {{0x60, 0xb3, 0xeb, 0x37}, {0xc1, 0xe5}, {0xea, 0x41}, 0x9d, 0xf3, {0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01} } + +''' + + name_contents = ''' + +toc_entry_t toc_entries[] = { + { + .name = "SCP Firmware Updater Configuration FWU SCP_BL2U", + .uuid = UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U, + .cmdline_name = "scp-fwu-cfg" + }, + { + .name = "AP Firmware Updater Configuration BL2U", + .uuid = UUID_TRUSTED_UPDATE_FIRMWARE_BL2U, + .cmdline_name = "ap-fwu-cfg" + }, +''' + + def setup_readme(self): + """Set up the readme.txt file""" + tools.WriteFile(self.readme, 'Trusted Firmware-A\n==================', + binary=False) + + def setup_macro(self, data=macro_contents): + """Set up the tbbr_config.c file""" + os.makedirs(self.macro_dir) + tools.WriteFile(self.macro_fname, data, binary=False) + + def setup_name(self, data=name_contents): + """Set up the firmware_image_package.h file""" + os.makedirs(self.name_dir) + tools.WriteFile(self.name_fname, data, binary=False) + + def tearDown(self): + """Remove the temporary input directory and its contents""" + if self._indir: + shutil.rmtree(self._indir) + self._indir = None + tools.FinaliseOutputDir() + + def test_no_readme(self): + """Test handling of a missing readme.rst""" + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('Expected file', str(err.exception)) + + def test_invalid_readme(self): + """Test that an invalid readme.rst is detected""" + tools.WriteFile(self.readme, 'blah', binary=False) + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('does not start with', str(err.exception)) + + def test_no_fip_h(self): + """Check handling of missing firmware_image_package.h""" + self.setup_readme() + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('No such file or directory', str(err.exception)) + + def test_invalid_fip_h(self): + """Check failure to parse firmware_image_package.h""" + self.setup_readme() + self.setup_macro('blah') + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('Cannot parse file', str(err.exception)) + + def test_parse_fip_h(self): + """Check parsing of firmware_image_package.h""" + self.setup_readme() + # Check parsing the header file + self.setup_macro() + macros = fip_util.parse_macros(self._indir) + expected_macros = { + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U': + ('ToC Entry UUIDs', 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U', + bytes([0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10])), + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U': + ('ToC Entry UUIDs', 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U', + bytes([0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41, + 0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01])), + } + self.assertEqual(expected_macros, macros) + + def test_missing_tbbr_c(self): + """Check handlinh of missing tbbr_config.c""" + self.setup_readme() + self.setup_macro() + + # Still need the .c file + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('tbbr_config.c', str(err.exception)) + + def test_invalid_tbbr_c(self): + """Check failure to parse tbbr_config.c""" + self.setup_readme() + self.setup_macro() + # Check invalid format for C file + self.setup_name('blah') + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('Cannot parse file', str(err.exception)) + + def test_inconsistent_tbbr_c(self): + """Check tbbr_config.c in a format we don't expect""" + self.setup_readme() + # This is missing a hex value + self.setup_macro(''' + +/* ToC Entry UUIDs */ +#define UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U \\ + {{0x65, 0x92, 0x27,}, {0x2f, 0x74}, {0xe6, 0x44}, 0x8d, 0xff, {0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10} } +#define UUID_TRUSTED_UPDATE_FIRMWARE_BL2U \\ + {{0x60, 0xb3, 0xeb, 0x37}, {0xc1, 0xe5}, {0xea, 0x41}, 0x9d, 0xf3, {0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01} } + +''') + # Check invalid format for C file + self.setup_name('blah') + with self.assertRaises(Exception) as err: + fip_util.main(self.args, self.src_file) + self.assertIn('Cannot parse UUID line 5', str(err.exception)) + + def test_parse_tbbr_c(self): + """Check parsing tbbr_config.c""" + self.setup_readme() + self.setup_macro() + self.setup_name() + + names = fip_util.parse_names(self._indir) + + expected_names = { + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U': ( + 'SCP Firmware Updater Configuration FWU SCP_BL2U', + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U', + 'scp-fwu-cfg'), + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U': ( + 'AP Firmware Updater Configuration BL2U', + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U', + 'ap-fwu-cfg'), + } + self.assertEqual(expected_names, names) + + def test_uuid_not_in_tbbr_config_c(self): + """Check handling a UUID in the header file that's not in the .c file""" + self.setup_readme() + self.setup_macro(self.macro_contents + ''' +#define UUID_TRUSTED_OS_FW_KEY_CERT \\ + {{0x94, 0x77, 0xd6, 0x03}, {0xfb, 0x60}, {0xe4, 0x11}, 0x85, 0xdd, {0xb7, 0x10, 0x5b, 0x8c, 0xee, 0x04} } + +''') + self.setup_name() + + macros = fip_util.parse_macros(self._indir) + names = fip_util.parse_names(self._indir) + with test_util.capture_sys_output() as (stdout, _): + fip_util.create_code_output(macros, names) + self.assertIn( + "UUID 'UUID_TRUSTED_OS_FW_KEY_CERT' is not mentioned in tbbr_config.c file", + stdout.getvalue()) + + def test_changes(self): + """Check handling of a source file that does/doesn't need changes""" + self.setup_readme() + self.setup_macro() + self.setup_name() + + # Check generating the file when changes are needed + tools.WriteFile(self.src_file, ''' + +# This is taken from tbbr_config.c in ARM Trusted Firmware +FIP_TYPE_LIST = [ + # ToC Entry UUIDs + FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U', + [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), + ] # end +blah de blah + ''', binary=False) + with test_util.capture_sys_output() as (stdout, _): + fip_util.main(self.args, self.src_file) + self.assertIn('Needs update', stdout.getvalue()) + + # Check generating the file when no changes are needed + tools.WriteFile(self.src_file, ''' +# This is taken from tbbr_config.c in ARM Trusted Firmware +FIP_TYPE_LIST = [ + # ToC Entry UUIDs + FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U', + [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), + FipType('ap-fwu-cfg', 'AP Firmware Updater Configuration BL2U', + [0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41, + 0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01]), + ] # end +blah blah''', binary=False) + with test_util.capture_sys_output() as (stdout, _): + fip_util.main(self.args, self.src_file) + self.assertIn('is up-to-date', stdout.getvalue()) + + def test_no_debug(self): + """Test running without the -D flag""" + self.setup_readme() + self.setup_macro() + self.setup_name() + + args = self.args.copy() + args.remove('-D') + tools.WriteFile(self.src_file, '', binary=False) + with test_util.capture_sys_output(): + fip_util.main(args, self.src_file) + + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') + def test_fiptool_list(self): + """Create a FIP and check that fiptool can read it""" + fwu = b'my data' + tb_fw = b'some more data' + fip = fip_util.FipWriter(0x123, 0x10) + fip.add_entry('fwu', fwu, 0x456) + fip.add_entry('tb-fw', tb_fw, 0) + fip.add_entry(bytes(range(16)), tb_fw, 0) + data = fip.get_data() + fname = tools.GetOutputFilename('data.fip') + tools.WriteFile(fname, data) + result = fip_util.fiptool('info', fname) + self.assertEqual( + '''Firmware Updater NS_BL2U: offset=0xB0, size=0x7, cmdline="--fwu" +Trusted Boot Firmware BL2: offset=0xC0, size=0xE, cmdline="--tb-fw" +00010203-0405-0607-0809-0A0B0C0D0E0F: offset=0xD0, size=0xE, cmdline="--blob" +''', + result.stdout) + + fwu_data = b'my data' + tb_fw_data = b'some more data' + other_fw_data = b'even more' + + def create_fiptool_image(self): + """Create an image with fiptool which we can use for testing + + Returns: + FipReader: reader for the image + """ + fwu = os.path.join(self._indir, 'fwu') + tools.WriteFile(fwu, self.fwu_data) + + tb_fw = os.path.join(self._indir, 'tb_fw') + tools.WriteFile(tb_fw, self.tb_fw_data) + + other_fw = os.path.join(self._indir, 'other_fw') + tools.WriteFile(other_fw, self.other_fw_data) + + fname = tools.GetOutputFilename('data.fip') + uuid = 'e3b78d9e-4a64-11ec-b45c-fba2b9b49788' + fip_util.fiptool('create', '--align', '8', '--plat-toc-flags', '0x123', + '--fwu', fwu, + '--tb-fw', tb_fw, + '--blob', f'uuid={uuid},file={other_fw}', + fname) + + return fip_util.FipReader(tools.ReadFile(fname)) + + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') + def test_fiptool_create(self): + """Create a FIP with fiptool and check that fip_util can read it""" + reader = self.create_fiptool_image() + + header = reader.header + fents = reader.fents + + self.assertEqual(0x123 << 32, header.flags) + self.assertEqual(fip_util.HEADER_MAGIC, header.name) + self.assertEqual(fip_util.HEADER_SERIAL, header.serial) + + self.assertEqual(3, len(fents)) + fent = fents[0] + self.assertEqual( + bytes([0x4f, 0x51, 0x1d, 0x11, 0x2b, 0xe5, 0x4e, 0x49, + 0xb4, 0xc5, 0x83, 0xc2, 0xf7, 0x15, 0x84, 0x0a]), fent.uuid) + self.assertEqual(0xb0, fent.offset) + self.assertEqual(len(self.fwu_data), fent.size) + self.assertEqual(0, fent.flags) + self.assertEqual(self.fwu_data, fent.data) + + fent = fents[1] + self.assertEqual( + bytes([0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d, + 0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a]), fent.uuid) + self.assertEqual(0xb8, fent.offset) + self.assertEqual(len(self.tb_fw_data), fent.size) + self.assertEqual(0, fent.flags) + self.assertEqual(self.tb_fw_data, fent.data) + + fent = fents[2] + self.assertEqual( + bytes([0xe3, 0xb7, 0x8d, 0x9e, 0x4a, 0x64, 0x11, 0xec, + 0xb4, 0x5c, 0xfb, 0xa2, 0xb9, 0xb4, 0x97, 0x88]), fent.uuid) + self.assertEqual(0xc8, fent.offset) + self.assertEqual(len(self.other_fw_data), fent.size) + self.assertEqual(0, fent.flags) + self.assertEqual(self.other_fw_data, fent.data) + + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') + def test_reader_get_entry(self): + """Test get_entry() by name and UUID""" + reader = self.create_fiptool_image() + fents = reader.fents + fent = reader.get_entry('fwu') + self.assertEqual(fent, fents[0]) + + fent = reader.get_entry( + bytes([0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d, + 0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a])) + self.assertEqual(fent, fents[1]) + + # Try finding entries that don't exist + with self.assertRaises(Exception) as err: + fent = reader.get_entry('scp-fwu-cfg') + self.assertIn("Cannot find FIP entry 'scp-fwu-cfg'", str(err.exception)) + + with self.assertRaises(Exception) as err: + fent = reader.get_entry(bytes(list(range(16)))) + self.assertIn( + "Cannot find FIP entry '00010203-0405-0607-0809-0a0b0c0d0e0f'", + str(err.exception)) + + with self.assertRaises(Exception) as err: + fent = reader.get_entry('blah') + self.assertIn("Unknown FIP entry type 'blah'", str(err.exception)) + + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') + def test_fiptool_errors(self): + """Check some error reporting from fiptool""" + with self.assertRaises(Exception) as err: + with test_util.capture_sys_output(): + fip_util.fiptool('create', '--fred') + self.assertIn("Failed to run (error 1): 'fiptool create --fred'", + str(err.exception)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/binman/main.py b/tools/binman/main.py index 8c1e478d54c..1a639f43e9e 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -59,6 +59,7 @@ def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath): from binman import elf_test from binman import entry_test from binman import fdt_test + from binman import fip_util_test from binman import ftest from binman import image_test import doctest @@ -72,7 +73,8 @@ def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath): result, debug, verbosity, test_preserve_dirs, processes, test_name, toolpath, [entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt, - elf_test.TestElf, image_test.TestImage, cbfs_util_test.TestCbfs]) + elf_test.TestElf, image_test.TestImage, cbfs_util_test.TestCbfs, + fip_util_test.TestFip]) return test_util.ReportResult('binman', test_name, result) -- cgit v1.2.3 From 75989727601b8fe6244314f90bc6f9ef3503b590 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 23 Nov 2021 21:08:59 -0700 Subject: binman: Add support for ATF FIP This format is used in firmware binaries so we may as well supported it. With this patch binman supports creating, listing and updating FIPs, as well as extracting files from one, provided that an FDTMAP is also present somewhere in the image. Signed-off-by: Simon Glass --- tools/binman/entries.rst | 154 +++++++++++++++++ tools/binman/etype/atf_fip.py | 273 +++++++++++++++++++++++++++++++ tools/binman/ftest.py | 217 ++++++++++++++++++++++++ tools/binman/test/203_fip.dts | 21 +++ tools/binman/test/204_fip_other.dts | 22 +++ tools/binman/test/205_fip_no_type.dts | 15 ++ tools/binman/test/206_fip_uuid.dts | 22 +++ tools/binman/test/207_fip_ls.dts | 25 +++ tools/binman/test/208_fip_replace.dts | 33 ++++ tools/binman/test/209_fip_missing.dts | 19 +++ tools/binman/test/210_fip_size.dts | 19 +++ tools/binman/test/211_fip_bad_align.dts | 18 ++ tools/binman/test/212_fip_collection.dts | 24 +++ 13 files changed, 862 insertions(+) create mode 100644 tools/binman/etype/atf_fip.py create mode 100644 tools/binman/test/203_fip.dts create mode 100644 tools/binman/test/204_fip_other.dts create mode 100644 tools/binman/test/205_fip_no_type.dts create mode 100644 tools/binman/test/206_fip_uuid.dts create mode 100644 tools/binman/test/207_fip_ls.dts create mode 100644 tools/binman/test/208_fip_replace.dts create mode 100644 tools/binman/test/209_fip_missing.dts create mode 100644 tools/binman/test/210_fip_size.dts create mode 100644 tools/binman/test/211_fip_bad_align.dts create mode 100644 tools/binman/test/212_fip_collection.dts (limited to 'tools') diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index d5aa3b0f4a4..c47f7df0980 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -25,6 +25,160 @@ about ATF. +Entry: atf-fip: ARM Trusted Firmware's Firmware Image Package (FIP) +------------------------------------------------------------------- + +A FIP_ provides a way to group binaries in a firmware image, used by ARM's +Trusted Firmware A (TF-A) code. It is a simple format consisting of a +table of contents with information about the type, offset and size of the +binaries in the FIP. It is quite similar to FMAP, with the major difference +that it uses UUIDs to indicate the type of each entry. + +Note: It is recommended to always add an fdtmap to every image, as well as +any FIPs so that binman and other tools can access the entire image +correctly. + +The UUIDs correspond to useful names in `fiptool`, provided by ATF to +operate on FIPs. Binman uses these names to make it easier to understand +what is going on, although it is possible to provide a UUID if needed. + +The contents of the FIP are defined by subnodes of the atf-fip entry, e.g.:: + + atf-fip { + soc-fw { + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + + u-boot { + fip-type = "nt-fw"; + }; + }; + +This describes a FIP with three entries: soc-fw, scp-fwu-cfg and nt-fw. +You can use normal (non-external) binaries like U-Boot simply by adding a +FIP type, with the `fip-type` property, as above. + +Since FIP exists to bring blobs together, Binman assumes that all FIP +entries are external binaries. If a binary may not exist, you can use the +`--allow-missing` flag to Binman, in which case the image is still created, +even though it will not actually work. + +The size of the FIP depends on the size of the binaries. There is currently +no way to specify a fixed size. If the `atf-fip` node has a `size` entry, +this affects the space taken up by the `atf-fip` entry, but the FIP itself +does not expand to use that space. + +Some other FIP features are available with Binman. The header and the +entries have 64-bit flag works. The flag flags do not seem to be defined +anywhere, but you can use `fip-hdr-flags` and fip-flags` to set the values +of the header and entries respectively. + +FIP entries can be aligned to a particular power-of-two boundary. Use +fip-align for this. + +Binman only understands the entry types that are included in its +implementation. It is possible to specify a 16-byte UUID instead, using the +fip-uuid property. In this case Binman doesn't know what its type is, so +just uses the UUID. See the `u-boot` node in this example:: + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + fip-align = <16>; + soc-fw { + fip-flags = /bits/ 64 <0x456>; + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + + u-boot { + fip-uuid = [fc 65 13 92 4a 5b 11 ec + 94 35 ff 2d 1c fc 79 9c]; + }; + }; + fdtmap { + }; + }; + +Binman allows reading and updating FIP entries after the image is created, +provided that an FDPMAP is present too. Updates which change the size of a +FIP entry will cause it to be expanded or contracted as needed. + +Properties for top-level atf-fip node +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +fip-hdr-flags (64 bits) + Sets the flags for the FIP header. + +Properties for subnodes +~~~~~~~~~~~~~~~~~~~~~~~ + +fip-type (str) + FIP type to use for this entry. This is needed if the entry + name is not a valid type. Value types are defined in `fip_util.py`. + The FIP type defines the UUID that is used (they map 1:1). + +fip-uuid (16 bytes) + If there is no FIP-type name defined, or it is not supported by Binman, + this property sets the UUID. It should be a 16-byte value, following the + hex digits of the UUID. + +fip-flags (64 bits) + Set the flags for a FIP entry. Use in one of the subnodes of the + 7atf-fip entry. + +fip-align + Set the alignment for a FIP entry, FIP entries can be aligned to a + particular power-of-two boundary. The default is 1. + +Adding new FIP-entry types +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When new FIP entries are defined by TF-A they appear in the +`TF-A source tree`_. You can use `fip_util.py` to update Binman to support +new types, then `send a patch`_ to the U-Boot mailing list. There are two +source files that the tool examples: + +- `include/tools_share/firmware_image_package.h` has the UUIDs +- `tools/fiptool/tbbr_config.c` has the name and descripion for each UUID + +To run the tool:: + + $ tools/binman/fip_util.py -s /path/to/arm-trusted-firmware + Warning: UUID 'UUID_NON_TRUSTED_WORLD_KEY_CERT' is not mentioned in tbbr_config.c file + Existing code in 'tools/binman/fip_util.py' is up-to-date + +If it shows there is an update, it writes a new version of `fip_util.py` +to `fip_util.py.out`. You can change the output file using the `-i` flag. +If you have a problem, use `-D` to enable traceback debugging. + +FIP commentary +~~~~~~~~~~~~~~ + +As a side effect of use of UUIDs, FIP does not support multiple +entries of the same type, such as might be used to store fonts or graphics +icons, for example. For verified boot it could be used for each part of the +image (e.g. separate FIPs for A and B) but cannot describe the whole +firmware image. As with FMAP there is no hierarchy defined, although FMAP +works around this by having 'section' areas which encompass others. A +similar workaround would be possible with FIP but is not currently defined. + +It is recommended to always add an fdtmap to every image, as well as any +FIPs so that binman and other tools can access the entire image correctly. + +.. _FIP: https://trustedfirmware-a.readthedocs.io/en/latest/design/firmware-design.html#firmware-image-package-fip +.. _`TF-A source tree`: https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git +.. _`send a patch`: https://www.denx.de/wiki/U-Boot/Patches + + + Entry: blob: Arbitrary binary blob ---------------------------------- diff --git a/tools/binman/etype/atf_fip.py b/tools/binman/etype/atf_fip.py new file mode 100644 index 00000000000..07e6c645b06 --- /dev/null +++ b/tools/binman/etype/atf_fip.py @@ -0,0 +1,273 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2019 Google LLC +# Written by Simon Glass +# +# Entry-type module for the ARM Trusted Firmware's Firmware Image Package (FIP) +# format + +from collections import OrderedDict + +from binman.entry import Entry +from binman.etype.section import Entry_section +from binman.fip_util import FIP_TYPES, FipReader, FipWriter, UUID_LEN +from dtoc import fdt_util +from patman import tools + +class Entry_atf_fip(Entry_section): + """ARM Trusted Firmware's Firmware Image Package (FIP) + + A FIP_ provides a way to group binaries in a firmware image, used by ARM's + Trusted Firmware A (TF-A) code. It is a simple format consisting of a + table of contents with information about the type, offset and size of the + binaries in the FIP. It is quite similar to FMAP, with the major difference + that it uses UUIDs to indicate the type of each entry. + + Note: It is recommended to always add an fdtmap to every image, as well as + any FIPs so that binman and other tools can access the entire image + correctly. + + The UUIDs correspond to useful names in `fiptool`, provided by ATF to + operate on FIPs. Binman uses these names to make it easier to understand + what is going on, although it is possible to provide a UUID if needed. + + The contents of the FIP are defined by subnodes of the atf-fip entry, e.g.:: + + atf-fip { + soc-fw { + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + + u-boot { + fip-type = "nt-fw"; + }; + }; + + This describes a FIP with three entries: soc-fw, scp-fwu-cfg and nt-fw. + You can use normal (non-external) binaries like U-Boot simply by adding a + FIP type, with the `fip-type` property, as above. + + Since FIP exists to bring blobs together, Binman assumes that all FIP + entries are external binaries. If a binary may not exist, you can use the + `--allow-missing` flag to Binman, in which case the image is still created, + even though it will not actually work. + + The size of the FIP depends on the size of the binaries. There is currently + no way to specify a fixed size. If the `atf-fip` node has a `size` entry, + this affects the space taken up by the `atf-fip` entry, but the FIP itself + does not expand to use that space. + + Some other FIP features are available with Binman. The header and the + entries have 64-bit flag works. The flag flags do not seem to be defined + anywhere, but you can use `fip-hdr-flags` and fip-flags` to set the values + of the header and entries respectively. + + FIP entries can be aligned to a particular power-of-two boundary. Use + fip-align for this. + + Binman only understands the entry types that are included in its + implementation. It is possible to specify a 16-byte UUID instead, using the + fip-uuid property. In this case Binman doesn't know what its type is, so + just uses the UUID. See the `u-boot` node in this example:: + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + fip-align = <16>; + soc-fw { + fip-flags = /bits/ 64 <0x456>; + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + + u-boot { + fip-uuid = [fc 65 13 92 4a 5b 11 ec + 94 35 ff 2d 1c fc 79 9c]; + }; + }; + fdtmap { + }; + }; + + Binman allows reading and updating FIP entries after the image is created, + provided that an FDPMAP is present too. Updates which change the size of a + FIP entry will cause it to be expanded or contracted as needed. + + Properties for top-level atf-fip node + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + fip-hdr-flags (64 bits) + Sets the flags for the FIP header. + + Properties for subnodes + ~~~~~~~~~~~~~~~~~~~~~~~ + + fip-type (str) + FIP type to use for this entry. This is needed if the entry + name is not a valid type. Value types are defined in `fip_util.py`. + The FIP type defines the UUID that is used (they map 1:1). + + fip-uuid (16 bytes) + If there is no FIP-type name defined, or it is not supported by Binman, + this property sets the UUID. It should be a 16-byte value, following the + hex digits of the UUID. + + fip-flags (64 bits) + Set the flags for a FIP entry. Use in one of the subnodes of the + 7atf-fip entry. + + fip-align + Set the alignment for a FIP entry, FIP entries can be aligned to a + particular power-of-two boundary. The default is 1. + + Adding new FIP-entry types + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + When new FIP entries are defined by TF-A they appear in the + `TF-A source tree`_. You can use `fip_util.py` to update Binman to support + new types, then `send a patch`_ to the U-Boot mailing list. There are two + source files that the tool examples: + + - `include/tools_share/firmware_image_package.h` has the UUIDs + - `tools/fiptool/tbbr_config.c` has the name and descripion for each UUID + + To run the tool:: + + $ tools/binman/fip_util.py -s /path/to/arm-trusted-firmware + Warning: UUID 'UUID_NON_TRUSTED_WORLD_KEY_CERT' is not mentioned in tbbr_config.c file + Existing code in 'tools/binman/fip_util.py' is up-to-date + + If it shows there is an update, it writes a new version of `fip_util.py` + to `fip_util.py.out`. You can change the output file using the `-i` flag. + If you have a problem, use `-D` to enable traceback debugging. + + FIP commentary + ~~~~~~~~~~~~~~ + + As a side effect of use of UUIDs, FIP does not support multiple + entries of the same type, such as might be used to store fonts or graphics + icons, for example. For verified boot it could be used for each part of the + image (e.g. separate FIPs for A and B) but cannot describe the whole + firmware image. As with FMAP there is no hierarchy defined, although FMAP + works around this by having 'section' areas which encompass others. A + similar workaround would be possible with FIP but is not currently defined. + + It is recommended to always add an fdtmap to every image, as well as any + FIPs so that binman and other tools can access the entire image correctly. + + .. _FIP: https://trustedfirmware-a.readthedocs.io/en/latest/design/firmware-design.html#firmware-image-package-fip + .. _`TF-A source tree`: https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git + .. _`send a patch`: https://www.denx.de/wiki/U-Boot/Patches + """ + def __init__(self, section, etype, node): + # Put this here to allow entry-docs and help to work without libfdt + global state + from binman import state + + super().__init__(section, etype, node) + self.align_default = None + self._entries = OrderedDict() + self.reader = None + + def ReadNode(self): + """Read properties from the atf-fip node""" + super().ReadNode() + self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) + self._fip_flags = fdt_util.GetInt64(self._node, 'fip-hdr-flags', 0) + self._fip_align = fdt_util.GetInt(self._node, 'fip-align', 1) + if tools.NotPowerOfTwo(self._fip_align): + raise ValueError("Node '%s': FIP alignment %s must be a power of two" % + (self._node.path, self._fip_align)) + self.ReadEntries() + + def ReadEntries(self): + """Read the subnodes to find out what should go in this FIP""" + for node in self._node.subnodes: + fip_type = None + etype = None + if node.name in FIP_TYPES: + fip_type = node.name + etype = 'blob-ext' + + entry = Entry.Create(self, node, etype) + entry._fip_uuid = fdt_util.GetBytes(node, 'fip-uuid', UUID_LEN) + if not fip_type and not entry._fip_uuid: + fip_type = fdt_util.GetString(node, 'fip-type') + if not fip_type: + self.Raise("Must provide a fip-type (node name '%s' is not a known FIP type)" % + node.name) + + entry._fip_type = fip_type + entry._fip_flags = fdt_util.GetInt64(node, 'fip-flags', 0) + entry.ReadNode() + entry._fip_name = node.name + self._entries[entry._fip_name] = entry + + def BuildSectionData(self, required): + """Override this function to create a custom format for the entries + + Arguments: + required (bool): True if the data must be valid, False if it may + be missing (entry.GetData() returns None + + Returns: + bytes: Data obtained, or None if None + """ + fip = FipWriter(self._fip_flags, self._fip_align) + for entry in self._entries.values(): + # First get the input data and put it in an entry. If not available, + # try later. + entry_data = entry.GetData(required) + if not required and entry_data is None: + return None + fent = fip.add_entry(entry._fip_type or entry._fip_uuid, entry_data, + entry._fip_flags) + if fent: + entry._fip_entry = fent + data = fip.get_data() + return data + + def SetImagePos(self, image_pos): + """Override this function to set all the entry properties from FIP + + We can only do this once image_pos is known + + Args: + image_pos: Position of this entry in the image + """ + super().SetImagePos(image_pos) + + # Now update the entries with info from the FIP entries + for entry in self._entries.values(): + fent = entry._fip_entry + entry.size = fent.size + entry.offset = fent.offset + entry.image_pos = self.image_pos + entry.offset + + def ReadChildData(self, child, decomp=True, alt_format=None): + if not self.reader: + self.fip_data = super().ReadData(True) + self.reader = FipReader(self.fip_data) + reader = self.reader + + # It is tricky to obtain the data from a FIP entry since it is indexed + # by its UUID. + fent = reader.get_entry(child._fip_type or child._fip_uuid) + return fent.data + + # Note: + # It is also possible to extract it using the offsets directly, but this + # seems less FIP_friendly: + # return self.fip_data[child.offset:child.offset + child.size] + + def WriteChildData(self, child): + # Recreate the data structure, leaving the data for this child alone, + # so that child.data is used to pack into the FIP. + self.ObtainContents(skip_entry=child) + return True diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index f5ceb9fb17d..2f3ec69709b 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -23,6 +23,7 @@ from binman import cmdline from binman import control from binman import elf from binman import elf_test +from binman import fip_util from binman import fmap_util from binman import state from dtoc import fdt @@ -76,6 +77,7 @@ FSP_M_DATA = b'fsp_m' FSP_S_DATA = b'fsp_s' FSP_T_DATA = b'fsp_t' ATF_BL31_DATA = b'bl31' +ATF_BL2U_DATA = b'bl2u' OPENSBI_DATA = b'opensbi' SCP_DATA = b'scp' TEST_FDT1_DATA = b'fdt1' @@ -179,6 +181,7 @@ class TestFunctional(unittest.TestCase): TestFunctional._MakeInputFile('compress', COMPRESS_DATA) TestFunctional._MakeInputFile('compress_big', COMPRESS_DATA_BIG) TestFunctional._MakeInputFile('bl31.bin', ATF_BL31_DATA) + TestFunctional._MakeInputFile('bl2u.bin', ATF_BL2U_DATA) TestFunctional._MakeInputFile('fw_dynamic.bin', OPENSBI_DATA) TestFunctional._MakeInputFile('scp.bin', SCP_DATA) @@ -4735,6 +4738,220 @@ fdt fdtmap Extract the devicetree blob from the fdtmap err = stderr.getvalue() self.assertRegex(err, "Image 'main-section'.*missing.*: blob-ext") + def testFip(self): + """Basic test of generation of an ARM Firmware Image Package (FIP)""" + data = self._DoReadFile('203_fip.dts') + hdr, fents = fip_util.decode_fip(data) + self.assertEqual(fip_util.HEADER_MAGIC, hdr.name) + self.assertEqual(fip_util.HEADER_SERIAL, hdr.serial) + self.assertEqual(0x123, hdr.flags) + + self.assertEqual(2, len(fents)) + + fent = fents[0] + self.assertEqual( + bytes([0x47, 0xd4, 0x08, 0x6d, 0x4c, 0xfe, 0x98, 0x46, + 0x9b, 0x95, 0x29, 0x50, 0xcb, 0xbd, 0x5a, 0x0]), fent.uuid) + self.assertEqual('soc-fw', fent.fip_type) + self.assertEqual(0x88, fent.offset) + self.assertEqual(len(ATF_BL31_DATA), fent.size) + self.assertEqual(0x123456789abcdef, fent.flags) + self.assertEqual(ATF_BL31_DATA, fent.data) + self.assertEqual(True, fent.valid) + + fent = fents[1] + self.assertEqual( + bytes([0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), fent.uuid) + self.assertEqual('scp-fwu-cfg', fent.fip_type) + self.assertEqual(0x8c, fent.offset) + self.assertEqual(len(ATF_BL31_DATA), fent.size) + self.assertEqual(0, fent.flags) + self.assertEqual(ATF_BL2U_DATA, fent.data) + self.assertEqual(True, fent.valid) + + def testFipOther(self): + """Basic FIP with something that isn't a external blob""" + data = self._DoReadFile('204_fip_other.dts') + hdr, fents = fip_util.decode_fip(data) + + self.assertEqual(2, len(fents)) + fent = fents[1] + self.assertEqual('rot-cert', fent.fip_type) + self.assertEqual(b'aa', fent.data) + + def testFipOther(self): + """Basic FIP with something that isn't a external blob""" + data = self._DoReadFile('204_fip_other.dts') + hdr, fents = fip_util.decode_fip(data) + + self.assertEqual(2, len(fents)) + fent = fents[1] + self.assertEqual('rot-cert', fent.fip_type) + self.assertEqual(b'aa', fent.data) + + def testFipNoType(self): + """FIP with an entry of an unknown type""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('205_fip_no_type.dts') + self.assertIn("Must provide a fip-type (node name 'u-boot' is not a known FIP type)", + str(e.exception)) + + def testFipUuid(self): + """Basic FIP with a manual uuid""" + data = self._DoReadFile('206_fip_uuid.dts') + hdr, fents = fip_util.decode_fip(data) + + self.assertEqual(2, len(fents)) + fent = fents[1] + self.assertEqual(None, fent.fip_type) + self.assertEqual( + bytes([0xfc, 0x65, 0x13, 0x92, 0x4a, 0x5b, 0x11, 0xec, + 0x94, 0x35, 0xff, 0x2d, 0x1c, 0xfc, 0x79, 0x9c]), + fent.uuid) + self.assertEqual(U_BOOT_DATA, fent.data) + + def testFipLs(self): + """Test listing a FIP""" + data = self._DoReadFileRealDtb('207_fip_ls.dts') + hdr, fents = fip_util.decode_fip(data) + + try: + tmpdir, updated_fname = self._SetupImageInTmpdir() + with test_util.capture_sys_output() as (stdout, stderr): + self._DoBinman('ls', '-i', updated_fname) + finally: + shutil.rmtree(tmpdir) + lines = stdout.getvalue().splitlines() + expected = [ +'Name Image-pos Size Entry-type Offset Uncomp-size', +'----------------------------------------------------------------', +'main-section 0 2d3 section 0', +' atf-fip 0 90 atf-fip 0', +' soc-fw 88 4 blob-ext 88', +' u-boot 8c 4 u-boot 8c', +' fdtmap 90 243 fdtmap 90', +] + self.assertEqual(expected, lines) + + image = control.images['image'] + entries = image.GetEntries() + fdtmap = entries['fdtmap'] + + fdtmap_data = data[fdtmap.image_pos:fdtmap.image_pos + fdtmap.size] + magic = fdtmap_data[:8] + self.assertEqual(b'_FDTMAP_', magic) + self.assertEqual(tools.GetBytes(0, 8), fdtmap_data[8:16]) + + fdt_data = fdtmap_data[16:] + dtb = fdt.Fdt.FromData(fdt_data) + dtb.Scan() + props = self._GetPropTree(dtb, BASE_DTB_PROPS, prefix='/') + self.assertEqual({ + 'atf-fip/soc-fw:image-pos': 136, + 'atf-fip/soc-fw:offset': 136, + 'atf-fip/soc-fw:size': 4, + 'atf-fip/u-boot:image-pos': 140, + 'atf-fip/u-boot:offset': 140, + 'atf-fip/u-boot:size': 4, + 'atf-fip:image-pos': 0, + 'atf-fip:offset': 0, + 'atf-fip:size': 144, + 'image-pos': 0, + 'offset': 0, + 'fdtmap:image-pos': fdtmap.image_pos, + 'fdtmap:offset': fdtmap.offset, + 'fdtmap:size': len(fdtmap_data), + 'size': len(data), + }, props) + + def testFipExtractOneEntry(self): + """Test extracting a single entry fron an FIP""" + self._DoReadFileRealDtb('207_fip_ls.dts') + image_fname = tools.GetOutputFilename('image.bin') + fname = os.path.join(self._indir, 'output.extact') + control.ExtractEntries(image_fname, fname, None, ['atf-fip/u-boot']) + data = tools.ReadFile(fname) + self.assertEqual(U_BOOT_DATA, data) + + def testFipReplace(self): + """Test replacing a single file in a FIP""" + expected = U_BOOT_DATA + tools.GetBytes(0x78, 50) + data = self._DoReadFileRealDtb('208_fip_replace.dts') + updated_fname = tools.GetOutputFilename('image-updated.bin') + tools.WriteFile(updated_fname, data) + entry_name = 'atf-fip/u-boot' + control.WriteEntry(updated_fname, entry_name, expected, + allow_resize=True) + actual = control.ReadEntry(updated_fname, entry_name) + self.assertEqual(expected, actual) + + new_data = tools.ReadFile(updated_fname) + hdr, fents = fip_util.decode_fip(new_data) + + self.assertEqual(2, len(fents)) + + # Check that the FIP entry is updated + fent = fents[1] + self.assertEqual(0x8c, fent.offset) + self.assertEqual(len(expected), fent.size) + self.assertEqual(0, fent.flags) + self.assertEqual(expected, fent.data) + self.assertEqual(True, fent.valid) + + def testFipMissing(self): + with test_util.capture_sys_output() as (stdout, stderr): + self._DoTestFile('209_fip_missing.dts', allow_missing=True) + err = stderr.getvalue() + self.assertRegex(err, "Image 'main-section'.*missing.*: rmm-fw") + + def testFipSize(self): + """Test a FIP with a size property""" + data = self._DoReadFile('210_fip_size.dts') + self.assertEqual(0x100 + len(U_BOOT_DATA), len(data)) + hdr, fents = fip_util.decode_fip(data) + self.assertEqual(fip_util.HEADER_MAGIC, hdr.name) + self.assertEqual(fip_util.HEADER_SERIAL, hdr.serial) + + self.assertEqual(1, len(fents)) + + fent = fents[0] + self.assertEqual('soc-fw', fent.fip_type) + self.assertEqual(0x60, fent.offset) + self.assertEqual(len(ATF_BL31_DATA), fent.size) + self.assertEqual(ATF_BL31_DATA, fent.data) + self.assertEqual(True, fent.valid) + + rest = data[0x60 + len(ATF_BL31_DATA):0x100] + self.assertEqual(tools.GetBytes(0xff, len(rest)), rest) + + def testFipBadAlign(self): + """Test that an invalid alignment value in a FIP is detected""" + with self.assertRaises(ValueError) as e: + self._DoTestFile('211_fip_bad_align.dts') + self.assertIn( + "Node \'/binman/atf-fip\': FIP alignment 31 must be a power of two", + str(e.exception)) + + def testFipCollection(self): + """Test using a FIP in a collection""" + data = self._DoReadFile('212_fip_collection.dts') + entry1 = control.images['image'].GetEntries()['collection'] + data1 = data[:entry1.size] + hdr1, fents2 = fip_util.decode_fip(data1) + + entry2 = control.images['image'].GetEntries()['atf-fip'] + data2 = data[entry2.offset:entry2.offset + entry2.size] + hdr1, fents2 = fip_util.decode_fip(data2) + + # The 'collection' entry should have U-Boot included at the end + self.assertEqual(entry1.size - len(U_BOOT_DATA), entry2.size) + self.assertEqual(data1, data2 + U_BOOT_DATA) + self.assertEqual(U_BOOT_DATA, data1[-4:]) + + # There should be a U-Boot after the final FIP + self.assertEqual(U_BOOT_DATA, data[-4:]) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/203_fip.dts b/tools/binman/test/203_fip.dts new file mode 100644 index 00000000000..08973373240 --- /dev/null +++ b/tools/binman/test/203_fip.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + soc-fw { + fip-flags = /bits/ 64 <0x123456789abcdef>; + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + }; + }; +}; diff --git a/tools/binman/test/204_fip_other.dts b/tools/binman/test/204_fip_other.dts new file mode 100644 index 00000000000..65039410986 --- /dev/null +++ b/tools/binman/test/204_fip_other.dts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + soc-fw { + fip-flags = /bits/ 64 <0x123456789abcdef>; + filename = "bl31.bin"; + }; + + _testing { + fip-type = "rot-cert"; + return-contents-later; + }; + }; + }; +}; diff --git a/tools/binman/test/205_fip_no_type.dts b/tools/binman/test/205_fip_no_type.dts new file mode 100644 index 00000000000..23c8c3bc37e --- /dev/null +++ b/tools/binman/test/205_fip_no_type.dts @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + u-boot { + }; + }; + }; +}; diff --git a/tools/binman/test/206_fip_uuid.dts b/tools/binman/test/206_fip_uuid.dts new file mode 100644 index 00000000000..c9bd44f9c31 --- /dev/null +++ b/tools/binman/test/206_fip_uuid.dts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + soc-fw { + fip-flags = /bits/ 64 <0x123456789abcdef>; + filename = "bl31.bin"; + }; + + u-boot { + fip-uuid = [fc 65 13 92 4a 5b 11 ec + 94 35 ff 2d 1c fc 79 9c]; + }; + }; + }; +}; diff --git a/tools/binman/test/207_fip_ls.dts b/tools/binman/test/207_fip_ls.dts new file mode 100644 index 00000000000..630fca15024 --- /dev/null +++ b/tools/binman/test/207_fip_ls.dts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + soc-fw { + fip-flags = /bits/ 64 <0x123456789abcdef>; + filename = "bl31.bin"; + }; + + u-boot { + fip-uuid = [fc 65 13 92 4a 5b 11 ec + 94 35 ff 2d 1c fc 79 9c]; + }; + }; + + fdtmap { + }; + }; +}; diff --git a/tools/binman/test/208_fip_replace.dts b/tools/binman/test/208_fip_replace.dts new file mode 100644 index 00000000000..432c12474df --- /dev/null +++ b/tools/binman/test/208_fip_replace.dts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + allow-repack; + atf-fip { + fip-hdr-flags = /bits/ 64 <0x123>; + soc-fw { + fip-flags = /bits/ 64 <0x123456789abcdef>; + filename = "bl31.bin"; + }; + + u-boot { + fip-uuid = [fc 65 13 92 4a 5b 11 ec + 94 35 ff 2d 1c fc 79 9c]; + }; + + }; + + u-boot { + }; + + u-boot-dtb { + }; + + fdtmap { + }; + }; +}; diff --git a/tools/binman/test/209_fip_missing.dts b/tools/binman/test/209_fip_missing.dts new file mode 100644 index 00000000000..43bb600d047 --- /dev/null +++ b/tools/binman/test/209_fip_missing.dts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + soc-fw { + filename = "bl31.bin"; + }; + + rmm-fw { + filename = "rmm.bin"; + }; + }; + }; +}; diff --git a/tools/binman/test/210_fip_size.dts b/tools/binman/test/210_fip_size.dts new file mode 100644 index 00000000000..9dfee796459 --- /dev/null +++ b/tools/binman/test/210_fip_size.dts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + size = <0x100>; + pad-byte = <0xff>; + soc-fw { + filename = "bl31.bin"; + }; + }; + u-boot { + }; + }; +}; diff --git a/tools/binman/test/211_fip_bad_align.dts b/tools/binman/test/211_fip_bad_align.dts new file mode 100644 index 00000000000..a0901496d80 --- /dev/null +++ b/tools/binman/test/211_fip_bad_align.dts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + atf-fip { + fip-align = <31>; + size = <0x100>; + pad-byte = <0xff>; + soc-fw { + filename = "bl31.bin"; + }; + }; + }; +}; diff --git a/tools/binman/test/212_fip_collection.dts b/tools/binman/test/212_fip_collection.dts new file mode 100644 index 00000000000..332c023af87 --- /dev/null +++ b/tools/binman/test/212_fip_collection.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + collection { + content = <&fip &u_boot>; + }; + fip: atf-fip { + soc-fw { + filename = "bl31.bin"; + }; + + scp-fwu-cfg { + filename = "bl2u.bin"; + }; + }; + u_boot: u-boot { + }; + }; +}; -- cgit v1.2.3 From 022f6b0643ee7cf6f9474b768ba2aa802762980b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 6 Dec 2021 14:44:12 +0300 Subject: binman: Do not pollute source tree when build with `make O=...` Importing libraries in Python caches the bytecode by default. Since we run scripts in source tree it ignores the current directory settings, which is $(srctree), and creates cache just in the middle of the source tree. Move cache to the current directory. Signed-off-by: Andy Shevchenko --- tools/binman/main.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/binman/main.py b/tools/binman/main.py index 1a639f43e9e..2fa55ff549a 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -16,9 +16,20 @@ import sys import traceback import unittest +# Get the absolute path to this file at run-time +our_path = os.path.dirname(os.path.realpath(__file__)) +our1_path = os.path.dirname(our_path) +our2_path = os.path.dirname(our1_path) + +# +# Do not pollute source tree with cache files: +# https://stackoverflow.com/a/60024195/2511795 +# https://bugs.python.org/issue33499 +# +sys.pycache_prefix = os.path.relpath(our_path, os.environ.get('srctree', our2_path)) + # Bring in the patman and dtoc libraries (but don't override the first path # in PYTHONPATH) -our_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(2, os.path.join(our_path, '..')) from patman import test_util -- cgit v1.2.3 From 33f27f4fadfaca3735f1ae35a20757aa21472b7c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 6 Dec 2021 14:44:13 +0300 Subject: binman: Use less hard coded magic when inserting new PATH Instead of joining hard coded '..' to the run-time path of the executable, take just a dirname out of it. Besides that, use $(srctree) where it makes sense. Signed-off-by: Andy Shevchenko --- tools/binman/main.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/binman/main.py b/tools/binman/main.py index 2fa55ff549a..35944f314a2 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -21,24 +21,26 @@ our_path = os.path.dirname(os.path.realpath(__file__)) our1_path = os.path.dirname(our_path) our2_path = os.path.dirname(our1_path) +# Extract $(srctree) from Kbuild environment, or use relative paths below +srctree = os.environ.get('srctree', our2_path) + # # Do not pollute source tree with cache files: # https://stackoverflow.com/a/60024195/2511795 # https://bugs.python.org/issue33499 # -sys.pycache_prefix = os.path.relpath(our_path, os.environ.get('srctree', our2_path)) +sys.pycache_prefix = os.path.relpath(our_path, srctree) # Bring in the patman and dtoc libraries (but don't override the first path # in PYTHONPATH) -sys.path.insert(2, os.path.join(our_path, '..')) +sys.path.insert(2, our1_path) from patman import test_util # Bring in the libfdt module sys.path.insert(2, 'scripts/dtc/pylibfdt') -sys.path.insert(2, os.path.join(our_path, '../../scripts/dtc/pylibfdt')) -sys.path.insert(2, os.path.join(our_path, - '../../build-sandbox_spl/scripts/dtc/pylibfdt')) +sys.path.insert(2, os.path.join(srctree, 'scripts/dtc/pylibfdt')) +sys.path.insert(2, os.path.join(srctree, 'build-sandbox_spl/scripts/dtc/pylibfdt')) # When running under python-coverage on Ubuntu 16.04, the dist-packages # directories are dropped from the python path. Add them in so that we can find -- cgit v1.2.3 From 1c11b5e6f67ca5aeb2d61e198298ed655311070e Mon Sep 17 00:00:00 2001 From: Ivan Mikhaylov Date: Thu, 9 Dec 2021 16:10:53 +0000 Subject: iot2050: binman: add missing-msg for blobs Add the 'missing-msg' for blobs for more detailed output on missing system firmware and SEBoot blobs. Signed-off-by: Ivan Mikhaylov Reviewed-by: Simon Glass Fix minor typos: Signed-off-by: Simon Glass --- tools/binman/missing-blob-help | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'tools') diff --git a/tools/binman/missing-blob-help b/tools/binman/missing-blob-help index dc2d9c98111..551ca87f6cb 100644 --- a/tools/binman/missing-blob-help +++ b/tools/binman/missing-blob-help @@ -18,6 +18,17 @@ scp-sunxi: SCP firmware is required for system suspend, but is otherwise optional. Please read the section on SCP firmware in board/sunxi/README.sunxi64 +iot2050-seboot: +See the documentation for IOT2050 board. Your image is missing SEBoot +which is mandatory for board startup. Prebuilt SEBoot located at +meta-iot2050/tree/master/recipes-bsp/u-boot/files/prebuild/tiboot3.bin. + +iot2050-sysfw: +See the documentation for IOT2050 board. Your image is missing system +firmware which is mandatory for board startup. Prebuilt system firmware +located at meta-iot2050/tree/master/recipes-bsp/u-boot/files/prebuild/ +with sysfw prefix. + k3-rti-wdt-firmware: If CONFIG_WDT_K3_RTI_LOAD_FW is enabled, a firmware image is needed for the R5F core(s) to trigger the system reset. One possible source is -- cgit v1.2.3 From 70ab7b1799112398047be63cc113f93955f0f352 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 8 Dec 2021 09:55:34 -0700 Subject: fdtgrep: Correct alignment of struct section When outputting a devicetree we should not align the struct section to a 16-byte boundary. The normal position is fine, which is 8-byte aligned. This avoids leaving adding 8 extra zero bytes in the output tree in the case where the reserved section is empty (i.e has 16 zero bytes). Signed-off-by: Simon Glass --- tools/fdtgrep.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/fdtgrep.c b/tools/fdtgrep.c index db512465db1..641d6a2e3e0 100644 --- a/tools/fdtgrep.c +++ b/tools/fdtgrep.c @@ -438,8 +438,7 @@ static int dump_fdt_regions(struct display_info *disp, const void *blob, fdt = (struct fdt_header *)out; memset(fdt, '\0', sizeof(*fdt)); fdt_set_magic(fdt, FDT_MAGIC); - struct_start = FDT_ALIGN(sizeof(struct fdt_header), - sizeof(struct fdt_reserve_entry)); + struct_start = sizeof(struct fdt_header); fdt_set_off_mem_rsvmap(fdt, struct_start); fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); -- cgit v1.2.3 From 8e0768124ffe683acb82cd70afad798f974382d1 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 16 Dec 2021 20:59:22 -0700 Subject: fdt: Drop CONFIG_BINMAN_STANDALONE_FDT This was added as a hack to work around not having an in-tree devicetree. Now that this is fixed it is not needed. Drop it. Signed-off-by: Simon Glass --- tools/binman/binman.rst | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'tools') diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 10389a52c4b..56f865800e6 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -232,26 +232,6 @@ You can use other, more specific CONFIG options - see 'Automatic .dtsi inclusion' below. -Using binman with OF_BOARD --------------------------------------------- - -Normally binman is used with a board configured with OF_SEPARATE or OF_EMBED. -This is a typical scenario where a device tree source that contains the binman -node is provided in the arch//dts directory for a specific board. - -However for a board configured with OF_BOARD, no device tree blob is provided -in the U-Boot build phase hence the binman node information is not available. -In order to support such use case, a new Kconfig option BINMAN_STANDALONE_FDT -is introduced, to tell the build system that a standalone device tree blob -containing binman node is explicitly required. - -Note there is a Kconfig option BINMAN_FDT which enables U-Boot run time to -access information about binman entries, stored in the device tree in a binman -node. Generally speaking, this option makes sense for OF_SEPARATE or OF_EMBED. -For the other OF_CONTROL methods, it's quite possible binman node is not -available as binman is invoked during the build phase, thus this option is not -turned on by default for these OF_CONTROL methods. - Access to binman entry offsets at run time (symbols) ---------------------------------------------------- -- cgit v1.2.3 From ed96683e009c9349005821ce790bc080ef572c22 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 16 Dec 2021 20:59:23 -0700 Subject: fdt: Make it easier to debug u-boot.dtsi files At present one must hack the Makefile to see what is going on with these files. Also it doesn't quite work correctly. Fix this by using an environment variable for debugging. Update the docs also. Signed-off-by: Simon Glass --- tools/binman/binman.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'tools') diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 56f865800e6..3e063d1f86c 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -790,12 +790,14 @@ Binman will search for the following files in arch//dts:: U-Boot will only use the first one that it finds. If you need to include a more general file you can do that from the more specific file using #include. -If you are having trouble figuring out what is going on, you can uncomment -the 'warning' line in scripts/Makefile.lib to see what it has found:: - - # Uncomment for debugging - # This shows all the files that were considered and the one that we chose. - # u_boot_dtsi_options_debug = $(u_boot_dtsi_options_raw) +If you are having trouble figuring out what is going on, you can use +`DEVICE_TREE_DEBUG=1` with your build:: + + make DEVICE_TREE_DEBUG=1 + scripts/Makefile.lib:334: Automatic .dtsi inclusion: options: + arch/arm/dts/juno-r2-u-boot.dtsi arch/arm/dts/-u-boot.dtsi + arch/arm/dts/armv8-u-boot.dtsi arch/arm/dts/armltd-u-boot.dtsi + arch/arm/dts/u-boot.dtsi ... found: "arch/arm/dts/juno-r2-u-boot.dtsi" Updating an ELF file -- cgit v1.2.3 From c1cad06f69a8f3207bdb20ad038db930c0f5c139 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 19 Nov 2021 13:23:58 -0700 Subject: video: Add a test for 16bpp BMP files Add a compressed 16bpp BMP file and a test to cover this. Signed-off-by: Simon Glass --- tools/logos/denx-16bpp.bmp.gz | Bin 0 -> 4516 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/logos/denx-16bpp.bmp.gz (limited to 'tools') diff --git a/tools/logos/denx-16bpp.bmp.gz b/tools/logos/denx-16bpp.bmp.gz new file mode 100644 index 00000000000..ed99c58192e Binary files /dev/null and b/tools/logos/denx-16bpp.bmp.gz differ -- cgit v1.2.3 From 4ea15482101bd2ef6d2086b1a8afb255de2e65e5 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 19 Nov 2021 13:23:59 -0700 Subject: video: theadorable: Use RGB565 for BMP blitting At present this uses RGB555 format for blitting to a display. Sandbox uses 565 and that seems to be more normal for BMP as well. Update the code accordingly and add a test. Note that this likely breaks the theadorable board so we may need to discuss supporting both formats. Signed-off-by: Simon Glass --- tools/logos/denx-24bpp.bmp.gz | Bin 0 -> 7137 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/logos/denx-24bpp.bmp.gz (limited to 'tools') diff --git a/tools/logos/denx-24bpp.bmp.gz b/tools/logos/denx-24bpp.bmp.gz new file mode 100644 index 00000000000..95b44d31950 Binary files /dev/null and b/tools/logos/denx-24bpp.bmp.gz differ -- cgit v1.2.3