diff options
-rw-r--r-- | tools/binman/btool/fdtgrep.py | 2 | ||||
-rw-r--r-- | tools/binman/control.py | 3 | ||||
-rw-r--r-- | tools/binman/entries.rst | 42 | ||||
-rw-r--r-- | tools/binman/entry.py | 4 | ||||
-rw-r--r-- | tools/binman/etype/alternates_fdt.py | 132 | ||||
-rw-r--r-- | tools/binman/ftest.py | 121 | ||||
-rw-r--r-- | tools/binman/image.py | 13 | ||||
-rw-r--r-- | tools/binman/test/328_alternates_fdt.dts | 28 | ||||
-rw-r--r-- | tools/binman/test/329_alternates_fdtgrep.dts | 29 | ||||
-rw-r--r-- | tools/binman/test/330_alternates_vpl.dts | 29 | ||||
-rw-r--r-- | tools/binman/test/331_alternates_spl.dts | 29 | ||||
-rw-r--r-- | tools/binman/test/332_alternates_inval.dts | 29 | ||||
-rw-r--r-- | tools/binman/test/alt_dts/model1.dts | 24 | ||||
-rw-r--r-- | tools/binman/test/alt_dts/model2.dts | 24 |
14 files changed, 507 insertions, 2 deletions
diff --git a/tools/binman/btool/fdtgrep.py b/tools/binman/btool/fdtgrep.py index c34d8d8943b..da1f8c7bf4e 100644 --- a/tools/binman/btool/fdtgrep.py +++ b/tools/binman/btool/fdtgrep.py @@ -84,7 +84,7 @@ class Bintoolfdtgrep(bintool.Bintool): elif phase == 'spl': tag = 'bootph-pre-ram' else: - raise(f"Invalid U-Boot phase '{phase}': Use tpl/vpl/spl") + raise ValueError(f"Invalid U-Boot phase '{phase}': Use tpl/vpl/spl") # These args mirror those in cmd_fdtgrep in scripts/Makefile.lib # First do the first stage diff --git a/tools/binman/control.py b/tools/binman/control.py index a233c778d5e..542c2b45644 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -734,6 +734,9 @@ def ProcessImage(image, update_fdt, write_map, get_contents=True, image.WriteMap() has_problems = CheckForProblems(image) + + image.WriteAlternates() + return has_problems def Binman(args): diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index 38dfe2c7db9..8bfec8b434e 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -11,6 +11,48 @@ features to produce new behaviours. +.. _etype_alternates_fdt: + +Entry: alternates-fdt: Entry that generates alternative sections for each devicetree provided +--------------------------------------------------------------------------------------------- + +When creating an image designed to boot on multiple models, each model +requires its own devicetree. This entry deals with selecting the correct +devicetree from a directory containing them. Each one is read in turn, then +used to produce section contents which are written to a file. This results +in a number of images, one for each model. + +For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + +Each output file is named based on its input file, so an input file of +`model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in +the `filename-pattern` property is replaced with the .dtb basename). + +Note that this entry type still produces contents for the 'main' image, in +that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. +But that image is unlikely to be useful, since it relates to whatever dtb +happened to be the default when U-Boot builds +(i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size +of each of the alternates is the same as the 'default' one, so they can in +principle be 'slotted in' to the appropriate place in the main image. + +The optional `fdt-phase` property indicates the phase to build. In this +case, it etype runs fdtgrep to obtain the devicetree subset for that phase, +respecting the `bootph-xxx` tags in the devicetree. + + + .. _etype_atf_bl31: Entry: atf-bl31: ARM Trusted Firmware (ATF) BL31 blob diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 494b1b1278d..6d2f3789940 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -1395,6 +1395,8 @@ features to produce new behaviours. 'u-boot-tpl-dtb' Returns: - bytes: Contents of requested FDT + tuple: + fname (str): Filename of .dtb + bytes: Contents of FDT (possibly run through fdtgrep) """ return self.section.FdtContents(fdt_etype) diff --git a/tools/binman/etype/alternates_fdt.py b/tools/binman/etype/alternates_fdt.py new file mode 100644 index 00000000000..808f535aa1b --- /dev/null +++ b/tools/binman/etype/alternates_fdt.py @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2024 Google LLC +# Written by Simon Glass <sjg@chromium.org> + +"""Entry-type module for producing multiple alternate sections""" + +import glob +import os + +from binman.entry import EntryArg +from binman.etype.section import Entry_section +from dtoc import fdt_util +from u_boot_pylib import tools + +class Entry_alternates_fdt(Entry_section): + """Entry that generates alternative sections for each devicetree provided + + When creating an image designed to boot on multiple models, each model + requires its own devicetree. This entry deals with selecting the correct + devicetree from a directory containing them. Each one is read in turn, then + used to produce section contents which are written to a file. This results + in a number of images, one for each model. + + For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + Each output file is named based on its input file, so an input file of + `model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in + the `filename-pattern` property is replaced with the .dtb basename). + + Note that this entry type still produces contents for the 'main' image, in + that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. + But that image is unlikely to be useful, since it relates to whatever dtb + happened to be the default when U-Boot builds + (i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size + of each of the alternates is the same as the 'default' one, so they can in + principle be 'slotted in' to the appropriate place in the main image. + + The optional `fdt-phase` property indicates the phase to build. In this + case, it etype runs fdtgrep to obtain the devicetree subset for that phase, + respecting the `bootph-xxx` tags in the devicetree. + """ + def __init__(self, section, etype, node): + super().__init__(section, etype, node) + self.fdt_list_dir = None + self.filename_pattern = None + self.required_props = ['fdt-list-dir'] + self._cur_fdt = None + self._fdt_phase = None + self.fdtgrep = None + self._fdt_dir = None + self._fdts = None + self._fname_pattern = None + self._remove_props = None + self.alternates = None + + def ReadNode(self): + """Read properties from the node""" + super().ReadNode() + self._fdt_dir = fdt_util.GetString(self._node, 'fdt-list-dir') + fname = tools.get_input_filename(self._fdt_dir) + fdts = glob.glob('*.dtb', root_dir=fname) + self._fdts = [os.path.splitext(f)[0] for f in fdts] + + self._fdt_phase = fdt_util.GetString(self._node, 'fdt-phase') + + # This is used by Image.WriteAlternates() + self.alternates = self._fdts + + self._fname_pattern = fdt_util.GetString(self._node, 'filename-pattern') + + self._remove_props = [] + props, = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False) + if props: + self._remove_props = props.split() + + def FdtContents(self, fdt_etype): + # If there is no current FDT, just use the normal one + if not self._cur_fdt: + return self.section.FdtContents(fdt_etype) + + # Find the file to use + fname = os.path.join(self._fdt_dir, f'{self._cur_fdt}.dtb') + infile = tools.get_input_filename(fname) + + # Run fdtgrep if needed, to remove unwanted nodes and properties + if self._fdt_phase: + uniq = self.GetUniqueName() + outfile = tools.get_output_filename( + f'{uniq}.{self._cur_fdt}-{self._fdt_phase}.dtb') + self.fdtgrep.create_for_phase(infile, self._fdt_phase, outfile, + self._remove_props) + return outfile, tools.read_file(outfile) + return fname, tools.read_file(infile) + + def ProcessWithFdt(self, alt): + """Produce the contents of this entry, using a particular FDT blob + + Args: + alt (str): Name of the alternate + + Returns: + tuple: + str: Filename to use for the alternate's .bin file + bytes: Contents of this entry's section, using the selected FDT + """ + pattern = self._fname_pattern or 'NAME.bin' + fname = pattern.replace('NAME', alt) + + data = b'' + try: + self._cur_fdt = alt + self.ProcessContents() + data = self.GetPaddedData() + finally: + self._cur_fdt = None + return fname, data + + def AddBintools(self, btools): + super().AddBintools(btools) + self.fdtgrep = self.AddBintool(btools, 'fdtgrep') diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index d091855b8e3..684e960b582 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -7,6 +7,7 @@ # python -m unittest func_test.TestFunctional.testHelp import collections +import glob import gzip import hashlib from optparse import OptionParser @@ -7484,6 +7485,126 @@ fdt fdtmap Extract the devicetree blob from the fdtmap err, "Image '.*' is missing external blobs and is non-functional: .*") + def CheckAlternates(self, dts, phase, xpl_data): + """Run the test for the alterative-fdt etype + + Args: + dts (str): Devicetree file to process + phase (str): Phase to process ('spl', 'tpl' or 'vpl') + xpl_data (bytes): Expected data for the phase's binary + + Returns: + dict of .dtb files produced + key: str filename + value: Fdt object + """ + testdir = TestFunctional._MakeInputDir('dtb') + dtb_list = [] + for fname in glob.glob(f'{self.TestFile("alt_dts")}/*.dts'): + tmp_fname = fdt_util.EnsureCompiled(fname, testdir) + base = os.path.splitext(os.path.basename(fname))[0] + dtb_list.append(base + '.bin') + shutil.move(tmp_fname, os.path.join(testdir, base + '.dtb')) + + entry_args = { + f'{phase}-dtb': '1', + f'{phase}-bss-pad': 'y', + 'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of', + } + data = self._DoReadFileDtb(dts, use_real_dtb=True, update_dtb=True, + use_expanded=True, entry_args=entry_args)[0] + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + pad_len = 10 + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + + # Check the dtb is using the test file + dtb_data = rest[pad_len:] + dtb = fdt.Fdt.FromData(dtb_data) + dtb.Scan() + fdt_size = dtb.GetFdtObj().totalsize() + self.assertEqual('model-not-set', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + pad_len = 10 + + # Check the other output files + dtbs = {} + for fname in dtb_list: + pathname = tools.get_output_filename(fname) + self.assertTrue(os.path.exists(pathname)) + + data = tools.read_file(pathname) + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + rest = rest[pad_len:] + + dtb = fdt.Fdt.FromData(rest) + dtb.Scan() + dtbs[fname] = dtb + + expected = 'one' if '1' in fname else 'two' + self.assertEqual(f'u-boot,model-{expected}', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + # Make sure the FDT is the same size as the 'main' one + rest = rest[fdt_size:] + + self.assertEqual(b'', rest) + return dtbs + + def testAlternatesFdt(self): + """Test handling of alternates-fdt etype""" + self._SetupTplElf() + dtbs = self.CheckAlternates('328_alternates_fdt.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual(5, len(node.props.keys())) + + # Make sure the other node is still there + self.assertIsNotNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrep(self): + """Test handling of alternates-fdt etype using fdtgrep""" + self._SetupTplElf() + dtbs = self.CheckAlternates('329_alternates_fdtgrep.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual({'some-prop', 'not-a-prop-to-remove'}, + node.props.keys()) + + # Make sure the other node is gone + self.assertIsNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrepVpl(self): + """Test handling of alternates-fdt etype using fdtgrep with vpl""" + self._SetupVplElf() + dtbs = self.CheckAlternates('330_alternates_vpl.dts', 'vpl', + U_BOOT_VPL_NODTB_DATA) + + def testAlternatesFdtgrepSpl(self): + """Test handling of alternates-fdt etype using fdtgrep with spl""" + self._SetupSplElf() + dtbs = self.CheckAlternates('331_alternates_spl.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + + def testAlternatesFdtgrepInval(self): + """Test alternates-fdt etype using fdtgrep with invalid phase""" + self._SetupSplElf() + with self.assertRaises(ValueError) as e: + dtbs = self.CheckAlternates('332_alternates_inval.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + self.assertIn("Invalid U-Boot phase 'bad-phase': Use tpl/vpl/spl", + str(e.exception)) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/image.py b/tools/binman/image.py index c1be5cc23a2..702c9055585 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -193,6 +193,19 @@ class Image(section.Entry_section): os.remove(sname) os.symlink(fname, sname) + def WriteAlternates(self): + """Write out alternative devicetree blobs, each in its own file""" + alt_entry = self.FindEntryType('alternates-fdt') + if not alt_entry: + return + + for alt in alt_entry.alternates: + fname, data = alt_entry.ProcessWithFdt(alt) + pathname = tools.get_output_filename(fname) + tout.info(f"Writing alternate '{alt}' to '{pathname}'") + tools.write_file(pathname, data) + tout.info("Wrote %#x bytes" % len(data)) + def WriteMap(self): """Write a map of the image to a .map file diff --git a/tools/binman/test/328_alternates_fdt.dts b/tools/binman/test/328_alternates_fdt.dts new file mode 100644 index 00000000000..c913c8e4745 --- /dev/null +++ b/tools/binman/test/328_alternates_fdt.dts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/329_alternates_fdtgrep.dts b/tools/binman/test/329_alternates_fdtgrep.dts new file mode 100644 index 00000000000..41695281456 --- /dev/null +++ b/tools/binman/test/329_alternates_fdtgrep.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/330_alternates_vpl.dts b/tools/binman/test/330_alternates_vpl.dts new file mode 100644 index 00000000000..5b57069e2ab --- /dev/null +++ b/tools/binman/test/330_alternates_vpl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "vpl"; + + section { + u-boot-vpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/331_alternates_spl.dts b/tools/binman/test/331_alternates_spl.dts new file mode 100644 index 00000000000..882fefce34a --- /dev/null +++ b/tools/binman/test/331_alternates_spl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "spl"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/332_alternates_inval.dts b/tools/binman/test/332_alternates_inval.dts new file mode 100644 index 00000000000..8c145dd2449 --- /dev/null +++ b/tools/binman/test/332_alternates_inval.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "bad-phase"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model1.dts b/tools/binman/test/alt_dts/model1.dts new file mode 100644 index 00000000000..01e95e8fabe --- /dev/null +++ b/tools/binman/test/alt_dts/model1.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model One"; + compatible = "u-boot,model-one"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model2.dts b/tools/binman/test/alt_dts/model2.dts new file mode 100644 index 00000000000..7829c519772 --- /dev/null +++ b/tools/binman/test/alt_dts/model2.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model Two"; + compatible = "u-boot,model-two"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; |