binman: fit: Allow running fdtgrep on devicetree blobs
When using FIT to load firmware builds for multiple models, the FIT must include a common binary along with a number of devicetree blobs, one for each model. This is the same mechanism as is used for loading an OS. However, SPL builds do not normally use the full devicetree, but instead a cut-down version which various nodes and properties removed. Add a new fit,fdt-phase property to allow binman to produce these devicetree blobs. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
@@ -121,6 +121,8 @@ Use `spl_phase()` to find the current U-Boot phase, e.g. `PHASE_SPL`. You can
|
|||||||
also find the previous and next phase and get the phase name.
|
also find the previous and next phase and get the phase name.
|
||||||
|
|
||||||
|
|
||||||
|
.. _fdtgrep_filter:
|
||||||
|
|
||||||
Device tree
|
Device tree
|
||||||
-----------
|
-----------
|
||||||
The U-Boot device tree is filtered by the fdtgrep tools during the build
|
The U-Boot device tree is filtered by the fdtgrep tools during the build
|
||||||
|
@@ -953,6 +953,35 @@ The 'fit,compatible' property (if present) is replaced with the compatible
|
|||||||
string from the root node of the devicetree, so that things work correctly
|
string from the root node of the devicetree, so that things work correctly
|
||||||
with FIT's configuration-matching algortihm.
|
with FIT's configuration-matching algortihm.
|
||||||
|
|
||||||
|
Dealing with phases
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
FIT can be used to load firmware. In this case it may be necessary to run
|
||||||
|
the devicetree for each model through fdtgrep to remove unwanted properties.
|
||||||
|
The 'fit,fdt-phase' property can be provided to indicate the phase for which
|
||||||
|
the devicetree is intended.
|
||||||
|
|
||||||
|
For example this indicates that the FDT should be processed for VPL::
|
||||||
|
|
||||||
|
images {
|
||||||
|
@fdt-SEQ {
|
||||||
|
description = "fdt-NAME";
|
||||||
|
type = "flat_dt";
|
||||||
|
compression = "none";
|
||||||
|
fit,fdt-phase = "vpl";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Using this mechanism, it is possible to generate a FIT which can provide VPL
|
||||||
|
images for multiple models, with TPL selecting the correct model to use. The
|
||||||
|
same approach can of course be used for SPL images.
|
||||||
|
|
||||||
|
Note that the `of-spl-remove-props` entryarg can be used to indicate
|
||||||
|
additional properties to remove. It is often used to remove properties like
|
||||||
|
`clock-names` and `pinctrl-names` which are not needed in SPL builds.
|
||||||
|
|
||||||
|
See :ref:`fdtgrep_filter` for more information.
|
||||||
|
|
||||||
Generating nodes from an ELF file (split-elf)
|
Generating nodes from an ELF file (split-elf)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@@ -171,7 +171,7 @@ class Entry_fit(Entry_section):
|
|||||||
firmware = "atf";
|
firmware = "atf";
|
||||||
loadables = "uboot";
|
loadables = "uboot";
|
||||||
fdt = "fdt-SEQ";
|
fdt = "fdt-SEQ";
|
||||||
fit,compatible;
|
fit,compatible; // optional
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,9 +181,38 @@ class Entry_fit(Entry_section):
|
|||||||
Note that if no devicetree files are provided (with '-a of-list' as above)
|
Note that if no devicetree files are provided (with '-a of-list' as above)
|
||||||
then no nodes will be generated.
|
then no nodes will be generated.
|
||||||
|
|
||||||
The 'fit,compatible' property is replaced with the compatible string from
|
The 'fit,compatible' property (if present) is replaced with the compatible
|
||||||
the root node of the devicetree, so that things work correctly with FIT's
|
string from the root node of the devicetree, so that things work correctly
|
||||||
configuration-matching algortihm.
|
with FIT's configuration-matching algortihm.
|
||||||
|
|
||||||
|
Dealing with phases
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
FIT can be used to load firmware. In this case it may be necessary to run
|
||||||
|
the devicetree for each model through fdtgrep to remove unwanted properties.
|
||||||
|
The 'fit,fdt-phase' property can be provided to indicate the phase for which
|
||||||
|
the devicetree is intended.
|
||||||
|
|
||||||
|
For example this indicates that the FDT should be processed for VPL::
|
||||||
|
|
||||||
|
images {
|
||||||
|
@fdt-SEQ {
|
||||||
|
description = "fdt-NAME";
|
||||||
|
type = "flat_dt";
|
||||||
|
compression = "none";
|
||||||
|
fit,fdt-phase = "vpl";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Using this mechanism, it is possible to generate a FIT which can provide VPL
|
||||||
|
images for multiple models, with TPL selecting the correct model to use. The
|
||||||
|
same approach can of course be used for SPL images.
|
||||||
|
|
||||||
|
Note that the `of-spl-remove-props` entryarg can be used to indicate
|
||||||
|
additional properties to remove. It is often used to remove properties like
|
||||||
|
`clock-names` and `pinctrl-names` which are not needed in SPL builds.
|
||||||
|
|
||||||
|
See :ref:`fdtgrep_filter` for more information.
|
||||||
|
|
||||||
Generating nodes from an ELF file (split-elf)
|
Generating nodes from an ELF file (split-elf)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -368,8 +397,14 @@ class Entry_fit(Entry_section):
|
|||||||
self._fdts = None
|
self._fdts = None
|
||||||
self._fdt_dir = None
|
self._fdt_dir = None
|
||||||
self.mkimage = None
|
self.mkimage = None
|
||||||
|
self.fdtgrep = None
|
||||||
self._priv_entries = {}
|
self._priv_entries = {}
|
||||||
self._loadables = []
|
self._loadables = []
|
||||||
|
self._remove_props = []
|
||||||
|
props, = self.GetEntryArgsOrProps(
|
||||||
|
[EntryArg('of-spl-remove-props', str)], required=False)
|
||||||
|
if props:
|
||||||
|
self._remove_props = props.split()
|
||||||
|
|
||||||
def ReadNode(self):
|
def ReadNode(self):
|
||||||
super().ReadNode()
|
super().ReadNode()
|
||||||
@@ -505,6 +540,19 @@ class Entry_fit(Entry_section):
|
|||||||
rel_path = node.path[len(self._node.path) + 1:]
|
rel_path = node.path[len(self._node.path) + 1:]
|
||||||
self.Raise(f"subnode '{rel_path}': {msg}")
|
self.Raise(f"subnode '{rel_path}': {msg}")
|
||||||
|
|
||||||
|
def _run_fdtgrep(self, infile, phase, outfile):
|
||||||
|
"""Run fdtgrep to create the dtb for a phase
|
||||||
|
|
||||||
|
Args:
|
||||||
|
infile (str): Input filename containing the full FDT contents (with
|
||||||
|
all nodes and properties)
|
||||||
|
phase (str): Phase to generate for ('tpl', 'vpl', 'spl')
|
||||||
|
outfile (str): Output filename to write the grepped FDT contents to
|
||||||
|
(with only neceesary nodes and properties)
|
||||||
|
"""
|
||||||
|
return self.fdtgrep.create_for_phase(infile, phase, outfile,
|
||||||
|
self._remove_props)
|
||||||
|
|
||||||
def _build_input(self):
|
def _build_input(self):
|
||||||
"""Finish the FIT by adding the 'data' properties to it
|
"""Finish the FIT by adding the 'data' properties to it
|
||||||
|
|
||||||
@@ -606,6 +654,7 @@ class Entry_fit(Entry_section):
|
|||||||
for seq, fdt_fname in enumerate(self._fdts):
|
for seq, fdt_fname in enumerate(self._fdts):
|
||||||
node_name = node.name[1:].replace('SEQ', str(seq + 1))
|
node_name = node.name[1:].replace('SEQ', str(seq + 1))
|
||||||
fname = tools.get_input_filename(fdt_fname + '.dtb')
|
fname = tools.get_input_filename(fdt_fname + '.dtb')
|
||||||
|
fdt_phase = None
|
||||||
with fsw.add_node(node_name):
|
with fsw.add_node(node_name):
|
||||||
for pname, prop in node.props.items():
|
for pname, prop in node.props.items():
|
||||||
if pname == 'fit,firmware':
|
if pname == 'fit,firmware':
|
||||||
@@ -623,6 +672,8 @@ class Entry_fit(Entry_section):
|
|||||||
fdt.Scan()
|
fdt.Scan()
|
||||||
prop = fdt.GetRoot().props['compatible']
|
prop = fdt.GetRoot().props['compatible']
|
||||||
fsw.property('compatible', prop.bytes)
|
fsw.property('compatible', prop.bytes)
|
||||||
|
elif pname == 'fit,fdt-phase':
|
||||||
|
fdt_phase = fdt_util.GetString(node, pname)
|
||||||
elif pname.startswith('fit,'):
|
elif pname.startswith('fit,'):
|
||||||
self._raise_subnode(
|
self._raise_subnode(
|
||||||
node, f"Unknown directive '{pname}'")
|
node, f"Unknown directive '{pname}'")
|
||||||
@@ -635,7 +686,14 @@ class Entry_fit(Entry_section):
|
|||||||
|
|
||||||
# Add data for 'images' nodes (but not 'config')
|
# Add data for 'images' nodes (but not 'config')
|
||||||
if depth == 1 and in_images:
|
if depth == 1 and in_images:
|
||||||
fsw.property('data', tools.read_file(fname))
|
if fdt_phase:
|
||||||
|
phase_fname = tools.get_output_filename(
|
||||||
|
f'{fdt_fname}-{fdt_phase}.dtb')
|
||||||
|
self._run_fdtgrep(fname, fdt_phase, phase_fname)
|
||||||
|
data = tools.read_file(phase_fname)
|
||||||
|
else:
|
||||||
|
data = tools.read_file(fname)
|
||||||
|
fsw.property('data', data)
|
||||||
|
|
||||||
for subnode in node.subnodes:
|
for subnode in node.subnodes:
|
||||||
with fsw.add_node(subnode.name):
|
with fsw.add_node(subnode.name):
|
||||||
@@ -863,6 +921,7 @@ class Entry_fit(Entry_section):
|
|||||||
def AddBintools(self, btools):
|
def AddBintools(self, btools):
|
||||||
super().AddBintools(btools)
|
super().AddBintools(btools)
|
||||||
self.mkimage = self.AddBintool(btools, 'mkimage')
|
self.mkimage = self.AddBintool(btools, 'mkimage')
|
||||||
|
self.fdtgrep = self.AddBintool(btools, 'fdtgrep')
|
||||||
|
|
||||||
def CheckMissing(self, missing_list):
|
def CheckMissing(self, missing_list):
|
||||||
# We must use our private entry list for this since generator nodes
|
# We must use our private entry list for this since generator nodes
|
||||||
|
@@ -7655,6 +7655,41 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
|
|||||||
self.assertEqual(f'u-boot,model-{expected}',
|
self.assertEqual(f'u-boot,model-{expected}',
|
||||||
fnode.props['compatible'].value)
|
fnode.props['compatible'].value)
|
||||||
|
|
||||||
|
def testFitFdtPhase(self):
|
||||||
|
"""Test an image with an FIT with fdt-phase in the fdt nodes"""
|
||||||
|
phase = 'tpl'
|
||||||
|
entry_args = {
|
||||||
|
f'{phase}-dtb': '1',
|
||||||
|
f'{phase}-bss-pad': 'y',
|
||||||
|
'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of',
|
||||||
|
'of-list': 'model1 model2',
|
||||||
|
'default-dt': 'model2',
|
||||||
|
}
|
||||||
|
testdir, dtb_list = self.SetupAlternateDts()
|
||||||
|
data = self._DoReadFileDtb(
|
||||||
|
'335_fit_fdt_phase.dts', use_real_dtb=True, update_dtb=True,
|
||||||
|
entry_args=entry_args, extra_indirs=[testdir])[0]
|
||||||
|
fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
|
||||||
|
fit = fdt.Fdt.FromData(fit_data)
|
||||||
|
fit.Scan()
|
||||||
|
|
||||||
|
# Check that each FDT has only the expected properties for the phase
|
||||||
|
for seq in range(1, 2):
|
||||||
|
fnode = fit.GetNode(f'/images/fdt-{seq}')
|
||||||
|
self.assertIsNotNone(fnode)
|
||||||
|
dtb = fdt.Fdt.FromData(fnode.props['data'].bytes)
|
||||||
|
dtb.Scan()
|
||||||
|
|
||||||
|
# Make sure that the 'bootph-pre-sram' tag in /node protects it from
|
||||||
|
# removal
|
||||||
|
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'))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -122,6 +122,8 @@ def RunBinman(args):
|
|||||||
ret_code = RunTests(args.debug, args.verbosity, args.processes,
|
ret_code = RunTests(args.debug, args.verbosity, args.processes,
|
||||||
args.test_preserve_dirs, args.tests,
|
args.test_preserve_dirs, args.tests,
|
||||||
args.toolpath)
|
args.toolpath)
|
||||||
|
if args.debug and not test_util.use_concurrent:
|
||||||
|
print('Tests can run in parallel: pip install concurrencytest')
|
||||||
|
|
||||||
elif args.cmd == 'bintool-docs':
|
elif args.cmd == 'bintool-docs':
|
||||||
control.write_bintool_docs(bintool.Bintool.get_tool_list())
|
control.write_bintool_docs(bintool.Bintool.get_tool_list())
|
||||||
|
61
tools/binman/test/335_fit_fdt_phase.dts
Normal file
61
tools/binman/test/335_fit_fdt_phase.dts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// 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";
|
||||||
|
fit,fdt-phase = "tpl";
|
||||||
|
hash {
|
||||||
|
algo = "sha256";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
default = "@config-DEFAULT-SEQ";
|
||||||
|
@config-SEQ {
|
||||||
|
description = "conf-NAME.dtb";
|
||||||
|
firmware = "uboot";
|
||||||
|
loadables = "atf";
|
||||||
|
fdt = "fdt-SEQ";
|
||||||
|
fit,firmware = "tpl";
|
||||||
|
fit,compatible;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
u-boot-nodtb {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
Reference in New Issue
Block a user