[PATCH 5/7] binman: Support generating FITs with multiple dtbs

Simon Glass sjg at chromium.org
Sat Aug 22 04:36:56 CEST 2020


In some cases it is useful to generate a FIT which has a number of DTB
images, selectable by configuration. Add support for this in binman,
using a simple iterator and string substitution.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/binman/README.entries       | 42 +++++++++++++++-
 tools/binman/etype/fit.py         | 76 ++++++++++++++++++++++++++---
 tools/binman/ftest.py             | 80 +++++++++++++++++++++++++++++--
 tools/binman/test/166_fit_fdt.dts | 55 +++++++++++++++++++++
 4 files changed, 242 insertions(+), 11 deletions(-)
 create mode 100644 tools/binman/test/166_fit_fdt.dts

diff --git a/tools/binman/README.entries b/tools/binman/README.entries
index 85b8ec9fa73..2bf7655b7f0 100644
--- a/tools/binman/README.entries
+++ b/tools/binman/README.entries
@@ -335,6 +335,7 @@ For example, this creates an image containing a FIT with U-Boot SPL:
     binman {
         fit {
             description = "Test FIT";
+            fit,fdt-list = "of-list";
 
             images {
                 kernel at 1 {
@@ -353,7 +354,46 @@ For example, this creates an image containing a FIT with U-Boot SPL:
         };
     };
 
-Properties:
+U-Boot supports creating fdt and config nodes automatically. To do this,
+pass an of-list property (e.g. -a of-list=file1 file2). This tells binman
+that you want to generates nodes for two files: file1.dtb and file2.dtb
+The fit,fdt-list property (see above) indicates that of-list should be used.
+
+Then add a 'generator node', a node with a name starting with '@':
+
+    @fdt-SEQ {
+        description = "fdt-NAME";
+        type = "flat_dt";
+        compression = "none";
+    };
+
+This tells binman to create nodes fdt-1 and fdt-2 for each of your two
+files. All the properties you specify will be included in the node. This
+node acts like a template to generate the nodes. The generator node itself
+does not appear in the output - it is replaced with what binman generates.
+
+You can create config nodes in a similar way:
+
+    @config-SEQ {
+        description = "NAME";
+        firmware = "uboot";
+        loadables = "atf";
+        fdt = "fdt-SEQ";
+    };
+
+This tells binman to create nodes config-1 and config-2, i.e. a config for
+each of your two files.
+
+Available substitutions for '@' nodes are:
+
+    SEQ    Sequence number of the generated fdt (1, 2, ...)
+    NAME   Name of the dtb as provided (i.e. without adding '.dtb')
+
+Note that if no device tree files are provided (with '-a of-list' as above)
+then no nodes will be generated.
+
+
+Properties (in the 'fit' node itself):
     fit,external-offset: Indicates that the contents of the FIT are external
         and provides the external offset. This is passsed to mkimage via
         the -E and -p flags.
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 75712f44092..e45a99dfd73 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -8,7 +8,7 @@
 from collections import defaultdict, OrderedDict
 import libfdt
 
-from binman.entry import Entry
+from binman.entry import Entry, EntryArg
 from dtoc import fdt_util
 from dtoc.fdt import Fdt
 from patman import tools
@@ -27,6 +27,7 @@ class Entry_fit(Entry):
         binman {
             fit {
                 description = "Test FIT";
+                fit,fdt-list = "of-list";
 
                 images {
                     kernel at 1 {
@@ -45,7 +46,46 @@ class Entry_fit(Entry):
             };
         };
 
-    Properties:
+    U-Boot supports creating fdt and config nodes automatically. To do this,
+    pass an of-list property (e.g. -a of-list=file1 file2). This tells binman
+    that you want to generates nodes for two files: file1.dtb and file2.dtb
+    The fit,fdt-list property (see above) indicates that of-list should be used.
+
+    Then add a 'generator node', a node with a name starting with '@':
+
+        @fdt-SEQ {
+            description = "fdt-NAME";
+            type = "flat_dt";
+            compression = "none";
+        };
+
+    This tells binman to create nodes fdt-1 and fdt-2 for each of your two
+    files. All the properties you specify will be included in the node. This
+    node acts like a template to generate the nodes. The generator node itself
+    does not appear in the output - it is replaced with what binman generates.
+
+    You can create config nodes in a similar way:
+
+        @config-SEQ {
+            description = "NAME";
+            firmware = "uboot";
+            loadables = "atf";
+            fdt = "fdt-SEQ";
+        };
+
+    This tells binman to create nodes config-1 and config-2, i.e. a config for
+    each of your two files.
+
+    Available substitutions for '@' nodes are:
+
+        SEQ    Sequence number of the generated fdt (1, 2, ...)
+        NAME   Name of the dtb as provided (i.e. without adding '.dtb')
+
+    Note that if no device tree files are provided (with '-a of-list' as above)
+    then no nodes will be generated.
+
+
+    Properties (in the 'fit' node itself):
         fit,external-offset: Indicates that the contents of the FIT are external
             and provides the external offset. This is passsed to mkimage via
             the -E and -p flags.
@@ -64,6 +104,15 @@ class Entry_fit(Entry):
         self._fit = None
         self._fit_content = defaultdict(list)
         self._fit_props = {}
+        for pname, prop in self._node.props.items():
+            if pname.startswith('fit,'):
+                self._fit_props[pname] = prop
+
+        self._fdts = []
+        list_prop = self._fit_props.get('fit,fdt-list')
+        if list_prop:
+            fdts, = self.GetEntryArgsOrProps([EntryArg(list_prop.value, str)])
+            self._fdts = fdts and fdts.split() or []
 
     def ReadNode(self):
         self._ReadSubnodes()
@@ -84,13 +133,12 @@ class Entry_fit(Entry):
                   image
             """
             for pname, prop in node.props.items():
-                if pname.startswith('fit,'):
-                    self._fit_props[pname] = prop
-                else:
+                if not pname.startswith('fit,'):
                     fsw.property(pname, prop.bytes)
 
             rel_path = node.path[len(base_node.path):]
-            has_images = depth == 2 and rel_path.startswith('/images/')
+            in_images = rel_path.startswith('/images')
+            has_images = depth == 2 and in_images
             for subnode in node.subnodes:
                 if has_images and not (subnode.name.startswith('hash') or
                                        subnode.name.startswith('signature')):
@@ -100,6 +148,22 @@ class Entry_fit(Entry):
                     entry = Entry.Create(self.section, subnode)
                     entry.ReadNode()
                     self._fit_content[rel_path].append(entry)
+                elif subnode.name.startswith('@'):
+                    for seq, fdt_fname in enumerate(self._fdts):
+                        node_name = subnode.name[1:].replace('SEQ',
+                                                             str(seq + 1))
+                        fname = tools.GetInputFilename(fdt_fname + '.dtb')
+                        with fsw.add_node(node_name):
+                            for pname, prop in subnode.props.items():
+                                val = prop.bytes.replace(
+                                    b'NAME', tools.ToBytes(fdt_fname))
+                                val = val.replace(b'SEQ',
+                                                  tools.ToBytes(str(seq + 1)))
+                                fsw.property(pname, val)
+
+                                # Add data for 'fdt' nodes (but not 'config')
+                                if depth == 1 and in_images:
+                                    fsw.property('data', tools.ReadFile(fname))
                 else:
                     with fsw.add_node(subnode.name):
                         _AddNode(base_node, depth + 1, subnode)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 30c9c764327..67b86276724 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -76,6 +76,11 @@ FSP_M_DATA            = b'fsp_m'
 FSP_S_DATA            = b'fsp_s'
 FSP_T_DATA            = b'fsp_t'
 ATF_BL31_DATA         = b'bl31'
+TEST_FDT1_DATA        = b'fdt1'
+TEST_FDT2_DATA        = b'test-fdt2'
+
+# Subdirectory of the input dir to use to put test FDTs
+TEST_FDT_SUBDIR       = 'fdts'
 
 # The expected size for the device tree in some tests
 EXTRACT_DTB_SIZE = 0x3c9
@@ -171,6 +176,12 @@ class TestFunctional(unittest.TestCase):
         TestFunctional._MakeInputFile('compress', COMPRESS_DATA)
         TestFunctional._MakeInputFile('bl31.bin', ATF_BL31_DATA)
 
+        # Add a few .dtb files for testing
+        TestFunctional._MakeInputFile('%s/test-fdt1.dtb' % TEST_FDT_SUBDIR,
+                                      TEST_FDT1_DATA)
+        TestFunctional._MakeInputFile('%s/test-fdt2.dtb' % TEST_FDT_SUBDIR,
+                                      TEST_FDT2_DATA)
+
         # Travis-CI may have an old lz4
         cls.have_lz4 = True
         try:
@@ -288,7 +299,7 @@ class TestFunctional(unittest.TestCase):
 
     def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
                     entry_args=None, images=None, use_real_dtb=False,
-                    verbosity=None, allow_missing=False):
+                    verbosity=None, allow_missing=False, extra_indirs=None):
         """Run binman with a given test file
 
         Args:
@@ -308,6 +319,7 @@ class TestFunctional(unittest.TestCase):
             verbosity: Verbosity level to use (0-3, None=don't set it)
             allow_missing: Set the '--allow-missing' flag so that missing
                 external binaries just produce a warning instead of an error
+            extra_indirs: Extra input directories to add using -I
         """
         args = []
         if debug:
@@ -334,6 +346,9 @@ class TestFunctional(unittest.TestCase):
         if images:
             for image in images:
                 args += ['-i', image]
+        if extra_indirs:
+            for indir in extra_indirs:
+                args += ['-I', indir]
         return self._DoBinman(*args)
 
     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
@@ -383,7 +398,8 @@ class TestFunctional(unittest.TestCase):
         return dtb.GetContents()
 
     def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
-                       update_dtb=False, entry_args=None, reset_dtbs=True):
+                       update_dtb=False, entry_args=None, reset_dtbs=True,
+                       extra_indirs=None):
         """Run binman and return the resulting image
 
         This runs binman with a given test file and then reads the resulting
@@ -407,6 +423,7 @@ class TestFunctional(unittest.TestCase):
             reset_dtbs: With use_real_dtb the test dtb is overwritten by this
                 function. If reset_dtbs is True, then the original test dtb
                 is written back before this function finishes
+            extra_indirs: Extra input directories to add using -I
 
         Returns:
             Tuple:
@@ -430,7 +447,8 @@ class TestFunctional(unittest.TestCase):
 
         try:
             retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
-                    entry_args=entry_args, use_real_dtb=use_real_dtb)
+                    entry_args=entry_args, use_real_dtb=use_real_dtb,
+                    extra_indirs=extra_indirs)
             self.assertEqual(0, retcode)
             out_dtb_fname = tools.GetOutputFilename('u-boot.dtb.out')
 
@@ -3512,7 +3530,61 @@ class TestFunctional(unittest.TestCase):
         data = self._DoReadFile('165_atf_bl31.dts')
         self.assertEqual(ATF_BL31_DATA, data[:len(ATF_BL31_DATA)])
 
-ATF_BL31_DATA
+    def testFitFdt(self):
+        """Test an image with an FIT with multiple FDT images"""
+        def _CheckFdt(seq, expected_data):
+            """Check the FDT nodes
+
+            Args:
+                seq: Sequence number to check (0 or 1)
+                expected_data: Expected contents of 'data' property
+            """
+            name = 'fdt-%d' % seq
+            fnode = dtb.GetNode('/images/%s' % name)
+            self.assertIsNotNone(fnode)
+            self.assertEqual({'description','type', 'compression', 'data'},
+                             set(fnode.props.keys()))
+            self.assertEqual(expected_data, fnode.props['data'].bytes)
+            self.assertEqual('fdt-test-fdt%d.dtb' % seq,
+                             fnode.props['description'].value)
+        def _CheckConfig(seq, expected_data):
+            """Check the configuration nodes
+
+            Args:
+                seq: Sequence number to check (0 or 1)
+                expected_data: Expected contents of 'data' property
+            """
+            name = 'config-%d' % seq
+            fnode = dtb.GetNode('/configurations/%s' % name)
+            self.assertIsNotNone(fnode)
+            self.assertEqual({'description','firmware', 'loadables', 'fdt'},
+                             set(fnode.props.keys()))
+            self.assertEqual('conf-test-fdt%d.dtb' % seq,
+                             fnode.props['description'].value)
+            self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value)
+
+        entry_args = {
+            'of-list': 'test-fdt1 test-fdt2',
+        }
+        data = self._DoReadFileDtb(
+            '166_fit_fdt.dts',
+            entry_args=entry_args,
+            extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
+        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
+        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
+
+        dtb = fdt.Fdt.FromData(fit_data)
+        dtb.Scan()
+        fnode = dtb.GetNode('/images/kernel')
+        self.assertIn('data', fnode.props)
+
+        # Check all the properties in fdt-1 and fdt-2
+        _CheckFdt(1, TEST_FDT1_DATA)
+        _CheckFdt(2, TEST_FDT2_DATA)
+
+        # Check configurations
+        _CheckConfig(1, TEST_FDT1_DATA)
+        _CheckConfig(2, TEST_FDT2_DATA)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/test/166_fit_fdt.dts b/tools/binman/test/166_fit_fdt.dts
new file mode 100644
index 00000000000..89142e91017
--- /dev/null
+++ b/tools/binman/test/166_fit_fdt.dts
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		u-boot {
+		};
+		fit {
+			description = "test-desc";
+			#address-cells = <1>;
+			fit,fdt-list = "of-list";
+
+			images {
+				kernel {
+					description = "Vanilla Linux kernel";
+					type = "kernel";
+					arch = "ppc";
+					os = "linux";
+					compression = "gzip";
+					load = <00000000>;
+					entry = <00000000>;
+					hash-1 {
+						algo = "crc32";
+					};
+					hash-2 {
+						algo = "sha1";
+					};
+					u-boot {
+					};
+				};
+				@fdt-SEQ {
+					description = "fdt-NAME.dtb";
+					type = "flat_dt";
+					compression = "none";
+				};
+			};
+
+			configurations {
+				default = "config-1";
+				@config-SEQ {
+					description = "conf-NAME.dtb";
+					firmware = "uboot";
+					loadables = "atf";
+					fdt = "fdt-SEQ";
+				};
+			};
+		};
+		u-boot-nodtb {
+		};
+	};
+};
-- 
2.28.0.297.g1956fa8f8d-goog



More information about the U-Boot mailing list