
Hi Simon
On 20/07/24 16:19, Simon Glass wrote:
FIT provides a way to select between different devicetree blobs depending on the model. This works fine for U-Boot proper and allows SPL to select the correct blob for the current board at runtime. The boot sequence (SPL->U-Boot proper) is therefore covered by the existing feature set.
The first boot phase (typically TPL) cannot use FIT since SoC boot ROMs don't currently support it. Therefore the TPL image must be specific to each model it boots on.
To support booting on mulitple models, binman must therefore produce a separate TPL image for each model, even if the images for the rest of the phases are identical.
TPL needs to be packaged as an executable binary along with a reduced devicetree. When multiple models are supported, a reduced devicetree must be provided for each model.
U-Boot's build system is designed to build a single devicetree for SPL builds, so does not support this requirement.
Add a new 'alternatives' feature to Binman, allowing it to automatically subset a devicetree to produce the reduced devicetree for a particular phase for each supported model. With this it is possible to produce a separate TPL image for each of the models. The correct one can then be loaded onto a board, along with the common FIT image(s).
Realized I caught up too late to this thread, and it's already pulled in, anyways thanks for the effort this was needed.
Signed-off-by: Simon Glass sjg@chromium.org
tools/binman/btool/fdtgrep.py | 2 +- tools/binman/control.py | 3 + tools/binman/entries.rst | 42 ++++++ tools/binman/entry.py | 4 +- tools/binman/etype/alternates_fdt.py | 132 +++++++++++++++++++ tools/binman/ftest.py | 121 +++++++++++++++++ tools/binman/image.py | 13 ++ tools/binman/test/328_alternates_fdt.dts | 28 ++++ tools/binman/test/329_alternates_fdtgrep.dts | 29 ++++ tools/binman/test/330_alternates_vpl.dts | 29 ++++ tools/binman/test/331_alternates_spl.dts | 29 ++++ tools/binman/test/332_alternates_inval.dts | 29 ++++ tools/binman/test/alt_dts/model1.dts | 24 ++++ tools/binman/test/alt_dts/model2.dts | 24 ++++ 14 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 tools/binman/etype/alternates_fdt.py create mode 100644 tools/binman/test/328_alternates_fdt.dts create mode 100644 tools/binman/test/329_alternates_fdtgrep.dts create mode 100644 tools/binman/test/330_alternates_vpl.dts create mode 100644 tools/binman/test/331_alternates_spl.dts create mode 100644 tools/binman/test/332_alternates_inval.dts create mode 100644 tools/binman/test/alt_dts/model1.dts create mode 100644 tools/binman/test/alt_dts/model2.dts
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;
};
- };
+};