Generate FDT nodes as above. This is the default if there is no
`fit,operation` property.
+split-elf
+ Split an ELF file into a separate node for each segment.
+
Generating nodes from an FDT list (gen-fdt-nodes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
+Generating nodes from an ELF file (split-elf)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This uses the node as a template to generate multiple nodes. The following
+special properties are available:
+
+split-elf
+ Split an ELF file into a separate node for each segment. This uses the
+ node as a template to generate multiple nodes. The following special
+ properties are available:
+
+ fit,load
+ Generates a `load = <...>` property with the load address of the
+ segment
+
+ fit,entry
+ Generates a `entry = <...>` property with the entry address of the
+ ELF. This is only produced for the first entry
+
+ fit,data
+ Generates a `data = <...>` property with the contents of the segment
+
+ fit,loadables
+ Generates a `loadable = <...>` property with a list of the generated
+ nodes (including all nodes if this operation is used multiple times)
+
+
+Here is an example showing ATF, TEE and a device tree all combined::
+
+ fit {
+ description = "test-desc";
+ #address-cells = <1>;
+ fit,fdt-list = "of-list";
+
+ images {
+ u-boot {
+ description = "U-Boot (64-bit)";
+ type = "standalone";
+ os = "U-Boot";
+ arch = "arm64";
+ compression = "none";
+ load = <CONFIG_SYS_TEXT_BASE>;
+ u-boot-nodtb {
+ };
+ };
+ @fdt-SEQ {
+ description = "fdt-NAME.dtb";
+ type = "flat_dt";
+ compression = "none";
+ };
+ @atf-SEQ {
+ fit,operation = "split-elf";
+ description = "ARM Trusted Firmware";
+ type = "firmware";
+ arch = "arm64";
+ os = "arm-trusted-firmware";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ atf-bl31 {
+ };
+ };
+
+ @tee-SEQ {
+ fit,operation = "split-elf";
+ description = "TEE";
+ type = "tee";
+ arch = "arm64";
+ os = "tee";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ tee-os {
+ };
+ };
+ };
+
+ configurations {
+ default = "@config-DEFAULT-SEQ";
+ @config-SEQ {
+ description = "conf-NAME.dtb";
+ fdt = "fdt-SEQ";
+ firmware = "u-boot";
+ fit,loadables;
+ };
+ };
+ };
+
+If ATF-BL31 is available, this generates a node for each segment in the
+ELF file, for example::
+
+ images {
+ atf-1 {
+ data = <...contents of first segment...>;
+ data-offset = <0x00000000>;
+ entry = <0x00040000>;
+ load = <0x00040000>;
+ compression = "none";
+ os = "arm-trusted-firmware";
+ arch = "arm64";
+ type = "firmware";
+ description = "ARM Trusted Firmware";
+ };
+ atf-2 {
+ data = <...contents of second segment...>;
+ load = <0xff3b0000>;
+ compression = "none";
+ os = "arm-trusted-firmware";
+ arch = "arm64";
+ type = "firmware";
+ description = "ARM Trusted Firmware";
+ };
+ };
+
+The same applies for OP-TEE if that is available.
+
+If each binary is not available, the relevant template node (@atf-SEQ or
+@tee-SEQ) is removed from the output.
+
+This also generates a `config-xxx` node for each device tree in `of-list`.
+Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
+so you can use `CONFIG_OF_LIST` to define that list. In this example it is
+set up for `firefly-rk3399` with a single device tree and the default set
+with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
+is::
+
+ configurations {
+ default = "config-1";
+ config-1 {
+ loadables = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
+ description = "rk3399-firefly.dtb";
+ fdt = "fdt-1";
+ firmware = "u-boot";
+ };
+ };
+
+U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
+(ATF and TEE), then proceed with the boot.
+
Entry: fmap: An entry which contains an Fmap section
from binman.entry import Entry, EntryArg
from binman.etype.section import Entry_section
+from binman import elf
from dtoc import fdt_util
from dtoc.fdt import Fdt
from patman import tools
# Supported operations, with the fit,operation property
-OP_GEN_FDT_NODES = range(1)
+OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
OPERATIONS = {
'gen-fdt-nodes': OP_GEN_FDT_NODES,
+ 'split-elf': OP_SPLIT_ELF,
}
class Entry_fit(Entry_section):
Generate FDT nodes as above. This is the default if there is no
`fit,operation` property.
+ split-elf
+ Split an ELF file into a separate node for each segment.
+
Generating nodes from an FDT list (gen-fdt-nodes)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
+
+ Generating nodes from an ELF file (split-elf)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This uses the node as a template to generate multiple nodes. The following
+ special properties are available:
+
+ split-elf
+ Split an ELF file into a separate node for each segment. This uses the
+ node as a template to generate multiple nodes. The following special
+ properties are available:
+
+ fit,load
+ Generates a `load = <...>` property with the load address of the
+ segment
+
+ fit,entry
+ Generates a `entry = <...>` property with the entry address of the
+ ELF. This is only produced for the first entry
+
+ fit,data
+ Generates a `data = <...>` property with the contents of the segment
+
+ fit,loadables
+ Generates a `loadable = <...>` property with a list of the generated
+ nodes (including all nodes if this operation is used multiple times)
+
+
+ Here is an example showing ATF, TEE and a device tree all combined::
+
+ fit {
+ description = "test-desc";
+ #address-cells = <1>;
+ fit,fdt-list = "of-list";
+
+ images {
+ u-boot {
+ description = "U-Boot (64-bit)";
+ type = "standalone";
+ os = "U-Boot";
+ arch = "arm64";
+ compression = "none";
+ load = <CONFIG_SYS_TEXT_BASE>;
+ u-boot-nodtb {
+ };
+ };
+ @fdt-SEQ {
+ description = "fdt-NAME.dtb";
+ type = "flat_dt";
+ compression = "none";
+ };
+ @atf-SEQ {
+ fit,operation = "split-elf";
+ description = "ARM Trusted Firmware";
+ type = "firmware";
+ arch = "arm64";
+ os = "arm-trusted-firmware";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ atf-bl31 {
+ };
+ };
+
+ @tee-SEQ {
+ fit,operation = "split-elf";
+ description = "TEE";
+ type = "tee";
+ arch = "arm64";
+ os = "tee";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ tee-os {
+ };
+ };
+ };
+
+ configurations {
+ default = "@config-DEFAULT-SEQ";
+ @config-SEQ {
+ description = "conf-NAME.dtb";
+ fdt = "fdt-SEQ";
+ firmware = "u-boot";
+ fit,loadables;
+ };
+ };
+ };
+
+ If ATF-BL31 is available, this generates a node for each segment in the
+ ELF file, for example::
+
+ images {
+ atf-1 {
+ data = <...contents of first segment...>;
+ data-offset = <0x00000000>;
+ entry = <0x00040000>;
+ load = <0x00040000>;
+ compression = "none";
+ os = "arm-trusted-firmware";
+ arch = "arm64";
+ type = "firmware";
+ description = "ARM Trusted Firmware";
+ };
+ atf-2 {
+ data = <...contents of second segment...>;
+ load = <0xff3b0000>;
+ compression = "none";
+ os = "arm-trusted-firmware";
+ arch = "arm64";
+ type = "firmware";
+ description = "ARM Trusted Firmware";
+ };
+ };
+
+ The same applies for OP-TEE if that is available.
+
+ If each binary is not available, the relevant template node (@atf-SEQ or
+ @tee-SEQ) is removed from the output.
+
+ This also generates a `config-xxx` node for each device tree in `of-list`.
+ Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
+ so you can use `CONFIG_OF_LIST` to define that list. In this example it is
+ set up for `firefly-rk3399` with a single device tree and the default set
+ with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
+ is::
+
+ configurations {
+ default = "config-1";
+ config-1 {
+ loadables = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
+ description = "rk3399-firefly.dtb";
+ fdt = "fdt-1";
+ firmware = "u-boot";
+ };
+ };
+
+ U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
+ (ATF and TEE), then proceed with the boot.
"""
def __init__(self, section, etype, node):
"""
entries which are used to create the FIT, but should not be
processed as real entries. This is set up once we have the
entries
+ _loadables: List of generated split-elf nodes, each a node name
"""
super().__init__(section, etype, node)
self._fit = None
self._fdts = None
self.mkimage = None
self._priv_entries = {}
+ self._loadables = []
def ReadNode(self):
super().ReadNode()
return
fsw.property(pname, prop.bytes)
- def _gen_fdt_nodes(node, depth, in_images):
+ def _gen_fdt_nodes(base_node, node, depth, in_images):
"""Generate FDT nodes
This creates one node for each member of self._fdts using the
fname = tools.get_input_filename(fdt_fname + '.dtb')
with fsw.add_node(node_name):
for pname, prop in node.props.items():
- val = prop.bytes.replace(
- b'NAME', tools.to_bytes(fdt_fname))
- val = val.replace(
- b'SEQ', tools.to_bytes(str(seq + 1)))
- fsw.property(pname, val)
+ if pname == 'fit,loadables':
+ val = '\0'.join(self._loadables) + '\0'
+ fsw.property('loadables', val.encode('utf-8'))
+ elif pname == 'fit,operation':
+ pass
+ elif pname.startswith('fit,'):
+ self._raise_subnode(
+ node, f"Unknown directive '{pname}'")
+ else:
+ val = prop.bytes.replace(
+ b'NAME', tools.to_bytes(fdt_fname))
+ val = val.replace(
+ b'SEQ', tools.to_bytes(str(seq + 1)))
+ fsw.property(pname, val)
# Add data for 'images' nodes (but not 'config')
if depth == 1 and in_images:
else:
self.Raise("Generator node requires 'fit,fdt-list' property")
- def _gen_node(base_node, node, depth, in_images):
+ def _gen_split_elf(base_node, node, elf_data, missing):
+ """Add nodes for the ELF file, one per group of contiguous segments
+
+ Args:
+ base_node (Node): Template node from the binman definition
+ node (Node): Node to replace (in the FIT being built)
+ data (bytes): ELF-format data to process (may be empty)
+ missing (bool): True if any of the data is missing
+
+ """
+ # If any pieces are missing, skip this. The missing entries will
+ # show an error
+ if not missing:
+ try:
+ segments, entry = elf.read_loadable_segments(elf_data)
+ except ValueError as exc:
+ self._raise_subnode(node,
+ f'Failed to read ELF file: {str(exc)}')
+ for (seq, start, data) in segments:
+ node_name = node.name[1:].replace('SEQ', str(seq + 1))
+ with fsw.add_node(node_name):
+ loadables.append(node_name)
+ for pname, prop in node.props.items():
+ if not pname.startswith('fit,'):
+ fsw.property(pname, prop.bytes)
+ elif pname == 'fit,load':
+ fsw.property_u32('load', start)
+ elif pname == 'fit,entry':
+ if seq == 0:
+ fsw.property_u32('entry', entry)
+ elif pname == 'fit,data':
+ fsw.property('data', bytes(data))
+ elif pname != 'fit,operation':
+ self._raise_subnode(
+ node, f"Unknown directive '{pname}'")
+
+ def _gen_node(base_node, node, depth, in_images, entry):
"""Generate nodes from a template
This creates one node for each member of self._fdts using the
"""
oper = self._get_operation(base_node, node)
if oper == OP_GEN_FDT_NODES:
- _gen_fdt_nodes(node, depth, in_images)
+ _gen_fdt_nodes(base_node, node, depth, in_images)
+ elif oper == OP_SPLIT_ELF:
+ # Entry_section.ObtainContents() either returns True or
+ # raises an exception.
+ data = None
+ missing_list = []
+ entry.ObtainContents()
+ entry.Pack(0)
+ data = entry.GetData()
+ entry.CheckMissing(missing_list)
+
+ _gen_split_elf(base_node, node, data, bool(missing_list))
def _add_node(base_node, depth, node):
"""Add nodes to the output FIT
# fsw.add_node() or _add_node() for it.
pass
elif self.GetImage().generate and subnode.name.startswith('@'):
- _gen_node(base_node, subnode, depth, in_images)
+ entry = self._priv_entries.get(subnode_path)
+ _gen_node(base_node, subnode, depth, in_images, entry)
# This is a generator (template) entry, so remove it from
# the list of entries used by PackEntries(), etc. Otherwise
# it will appear in the binman output
fsw = libfdt.FdtSw()
fsw.finish_reservemap()
to_remove = []
+ loadables = []
with fsw.add_node(''):
_add_node(self._node, 0, self._node)
+ self._loadables = loadables
fdt = fsw.as_fdt()
# Remove generator entries from the main list
TestFunctional._MakeInputFile('env.txt', ENV_DATA)
+ # ELF file with two sections in different parts of memory, used for both
+ # ATF and OP_TEE
+ TestFunctional._MakeInputFile('bl31.elf',
+ tools.read_file(cls.ElfTestFile('elf_sections')))
+ TestFunctional._MakeInputFile('tee.elf',
+ tools.read_file(cls.ElfTestFile('elf_sections')))
+
cls.have_lz4 = comp_util.HAVE_LZ4
@classmethod
err,
"Image '.*' has faked external blobs and is non-functional: .*")
+ def testFitSplitElf(self):
+ """Test an image with an FIT with an split-elf operation"""
+ entry_args = {
+ 'of-list': 'test-fdt1 test-fdt2',
+ 'default-dt': 'test-fdt2',
+ 'atf-bl31-path': 'bl31.elf',
+ 'tee-os-path': 'tee.elf',
+ }
+ test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+ data = self._DoReadFileDtb(
+ '226_fit_split_elf.dts',
+ entry_args=entry_args,
+ extra_indirs=[test_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)]
+
+ base_keys = {'description', 'type', 'arch', 'os', 'compression',
+ 'data', 'load'}
+ dtb = fdt.Fdt.FromData(fit_data)
+ dtb.Scan()
+
+ elf_data = tools.read_file(os.path.join(self._indir, 'bl31.elf'))
+ segments, entry = elf.read_loadable_segments(elf_data)
+
+ # We assume there are two segments
+ self.assertEquals(2, len(segments))
+
+ atf1 = dtb.GetNode('/images/atf-1')
+ _, start, data = segments[0]
+ self.assertEqual(base_keys | {'entry'}, atf1.props.keys())
+ self.assertEqual(entry,
+ fdt_util.fdt32_to_cpu(atf1.props['entry'].value))
+ self.assertEqual(start,
+ fdt_util.fdt32_to_cpu(atf1.props['load'].value))
+ self.assertEqual(data, atf1.props['data'].bytes)
+
+ atf2 = dtb.GetNode('/images/atf-2')
+ self.assertEqual(base_keys, atf2.props.keys())
+ _, start, data = segments[1]
+ self.assertEqual(start,
+ fdt_util.fdt32_to_cpu(atf2.props['load'].value))
+ self.assertEqual(data, atf2.props['data'].bytes)
+
+ conf = dtb.GetNode('/configurations')
+ self.assertEqual({'default'}, conf.props.keys())
+
+ for subnode in conf.subnodes:
+ self.assertEqual({'description', 'fdt', 'loadables'},
+ subnode.props.keys())
+ self.assertEqual(
+ ['atf-1', 'atf-2', 'tee-1', 'tee-2'],
+ fdt_util.GetStringList(subnode, 'loadables'))
+
+ def _check_bad_fit(self, dts):
+ """Check a bad FIT
+
+ This runs with the given dts and returns the assertion raised
+
+ Args:
+ dts (str): dts filename to use
+
+ Returns:
+ str: Assertion string raised
+ """
+ entry_args = {
+ 'of-list': 'test-fdt1 test-fdt2',
+ 'default-dt': 'test-fdt2',
+ 'atf-bl31-path': 'bl31.elf',
+ 'tee-os-path': 'tee.elf',
+ }
+ test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+ with self.assertRaises(ValueError) as exc:
+ self._DoReadFileDtb(dts, entry_args=entry_args,
+ extra_indirs=[test_subdir])[0]
+ return str(exc.exception)
+
+ def testFitSplitElfBadElf(self):
+ """Test a FIT split-elf operation with an invalid ELF file"""
+ TestFunctional._MakeInputFile('bad.elf', tools.get_bytes(100, 100))
+ entry_args = {
+ 'of-list': 'test-fdt1 test-fdt2',
+ 'default-dt': 'test-fdt2',
+ 'atf-bl31-path': 'bad.elf',
+ 'tee-os-path': 'tee.elf',
+ }
+ test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+ with self.assertRaises(ValueError) as exc:
+ self._DoReadFileDtb(
+ '226_fit_split_elf.dts',
+ entry_args=entry_args,
+ extra_indirs=[test_subdir])[0]
+ self.assertIn(
+ "Node '/binman/fit': subnode 'images/@atf-SEQ': Failed to read ELF file: Magic number does not match",
+ str(exc.exception))
+
+ def testFitSplitElfBadDirective(self):
+ """Test a FIT split-elf invalid fit,xxx directive in an image node"""
+ err = self._check_bad_fit('227_fit_bad_dir.dts')
+ self.assertIn(
+ "Node '/binman/fit': subnode 'images/@atf-SEQ': Unknown directive 'fit,something'",
+ err)
+
+ def testFitSplitElfBadDirectiveConfig(self):
+ """Test a FIT split-elf with invalid fit,xxx directive in config"""
+ err = self._check_bad_fit('228_fit_bad_dir_config.dts')
+ self.assertEqual(
+ "Node '/binman/fit': subnode 'configurations/@config-SEQ': Unknown directive 'fit,config'",
+ err)
+
+ def checkFitSplitElf(self, **kwargs):
+ """Test an split-elf FIT with a missing ELF file"""
+ entry_args = {
+ 'of-list': 'test-fdt1 test-fdt2',
+ 'default-dt': 'test-fdt2',
+ 'atf-bl31-path': 'bl31.elf',
+ 'tee-os-path': 'missing.elf',
+ }
+ test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+ with test_util.capture_sys_output() as (stdout, stderr):
+ self._DoTestFile(
+ '226_fit_split_elf.dts', entry_args=entry_args,
+ extra_indirs=[test_subdir], **kwargs)
+ err = stderr.getvalue()
+ return err
+
+ def testFitSplitElfMissing(self):
+ """Test an split-elf FIT with a missing ELF file"""
+ err = self.checkFitSplitElf(allow_missing=True)
+ self.assertRegex(
+ err,
+ "Image '.*' is missing external blobs and is non-functional: .*")
+
+ def testFitSplitElfFaked(self):
+ """Test an split-elf FIT with faked ELF file"""
+ err = self.checkFitSplitElf(allow_missing=True, allow_fake_blobs=True)
+ self.assertRegex(
+ err,
+ "Image '.*' is missing external blobs and is non-functional: .*")
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+// 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 {
+ @fdt-SEQ {
+ description = "fdt-NAME.dtb";
+ type = "flat_dt";
+ compression = "none";
+ };
+ atf: @atf-SEQ {
+ fit,operation = "split-elf";
+ description = "ARM Trusted Firmware";
+ type = "firmware";
+ arch = "arm64";
+ os = "arm-trusted-firmware";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ atf-bl31 {
+ };
+ };
+
+ @tee-SEQ {
+ fit,operation = "split-elf";
+ description = "TEE";
+ type = "tee";
+ arch = "arm64";
+ os = "tee";
+ compression = "none";
+ fit,load;
+ fit,entry;
+ fit,data;
+
+ tee-os {
+ };
+ };
+ };
+
+ configurations {
+ default = "@config-DEFAULT-SEQ";
+ config: @config-SEQ {
+ description = "conf-NAME.dtb";
+ fdt = "fdt-SEQ";
+ fit,loadables;
+ };
+ };
+ };
+
+ u-boot-nodtb {
+ };
+ };
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#include "226_fit_split_elf.dts"
+
+&atf {
+ fit,something = "bad";
+};
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+#include "226_fit_split_elf.dts"
+
+&config {
+ fit,config = "bad";
+};