1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
6 """Entry-type module for producing a FIT"""
10 from binman.entry import Entry, EntryArg
11 from binman.etype.section import Entry_section
12 from binman import elf
13 from dtoc import fdt_util
14 from dtoc.fdt import Fdt
15 from patman import tools
17 # Supported operations, with the fit,operation property
18 OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
20 'gen-fdt-nodes': OP_GEN_FDT_NODES,
21 'split-elf': OP_SPLIT_ELF,
24 class Entry_fit(Entry_section):
26 """Flat Image Tree (FIT)
28 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
31 Nodes for the FIT should be written out in the binman configuration just as
32 they would be in a file passed to mkimage.
34 For example, this creates an image containing a FIT with U-Boot SPL::
38 description = "Test FIT";
39 fit,fdt-list = "of-list";
58 More complex setups can be created, with generated nodes, as described
61 Properties (in the 'fit' node itself)
62 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64 Special properties have a `fit,` prefix, indicating that they should be
65 processed but not included in the final FIT.
67 The top-level 'fit' node supports the following special properties:
70 Indicates that the contents of the FIT are external and provides the
71 external offset. This is passed to mkimage via the -E and -p flags.
74 Indicates the entry argument which provides the list of device tree
75 files for the gen-fdt-nodes operation (as below). This is often
76 `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
82 Node names and property values support a basic string-substitution feature.
83 Available substitutions for '@' nodes (and property values) are:
86 Sequence number of the generated fdt (1, 2, ...)
88 Name of the dtb as provided (i.e. without adding '.dtb')
90 The `default` property, if present, will be automatically set to the name
91 if of configuration whose devicetree matches the `default-dt` entry
92 argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
94 Available substitutions for property values in these nodes are:
97 Sequence number of the default fdt, as provided by the 'default-dt'
103 You can add an operation to an '@' node to indicate which operation is
107 fit,operation = "gen-fdt-nodes";
111 Available operations are:
114 Generate FDT nodes as above. This is the default if there is no
115 `fit,operation` property.
118 Split an ELF file into a separate node for each segment.
120 Generating nodes from an FDT list (gen-fdt-nodes)
121 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123 U-Boot supports creating fdt and config nodes automatically. To do this,
124 pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
125 binman that you want to generates nodes for two files: `file1.dtb` and
126 `file2.dtb`. The `fit,fdt-list` property (see above) indicates that
127 `of-list` should be used. If the property is missing you will get an error.
129 Then add a 'generator node', a node with a name starting with '@'::
133 description = "fdt-NAME";
135 compression = "none";
139 This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
140 files. All the properties you specify will be included in the node. This
141 node acts like a template to generate the nodes. The generator node itself
142 does not appear in the output - it is replaced with what binman generates.
143 A 'data' property is created with the contents of the FDT file.
145 You can create config nodes in a similar way::
148 default = "@config-DEFAULT-SEQ";
150 description = "NAME";
157 This tells binman to create nodes `config-1` and `config-2`, i.e. a config
158 for each of your two files.
160 Note that if no devicetree files are provided (with '-a of-list' as above)
161 then no nodes will be generated.
163 Generating nodes from an ELF file (split-elf)
164 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
166 This uses the node as a template to generate multiple nodes. The following
167 special properties are available:
170 Split an ELF file into a separate node for each segment. This uses the
171 node as a template to generate multiple nodes. The following special
172 properties are available:
175 Generates a `load = <...>` property with the load address of the
179 Generates a `entry = <...>` property with the entry address of the
180 ELF. This is only produced for the first entry
183 Generates a `data = <...>` property with the contents of the segment
186 Generates a `loadable = <...>` property with a list of the generated
187 nodes (including all nodes if this operation is used multiple times)
190 Here is an example showing ATF, TEE and a device tree all combined::
193 description = "test-desc";
194 #address-cells = <1>;
195 fit,fdt-list = "of-list";
199 description = "U-Boot (64-bit)";
203 compression = "none";
204 load = <CONFIG_SYS_TEXT_BASE>;
209 description = "fdt-NAME.dtb";
211 compression = "none";
214 fit,operation = "split-elf";
215 description = "ARM Trusted Firmware";
218 os = "arm-trusted-firmware";
219 compression = "none";
229 fit,operation = "split-elf";
234 compression = "none";
245 default = "@config-DEFAULT-SEQ";
247 description = "conf-NAME.dtb";
255 If ATF-BL31 is available, this generates a node for each segment in the
256 ELF file, for example::
260 data = <...contents of first segment...>;
261 data-offset = <0x00000000>;
262 entry = <0x00040000>;
264 compression = "none";
265 os = "arm-trusted-firmware";
268 description = "ARM Trusted Firmware";
271 data = <...contents of second segment...>;
273 compression = "none";
274 os = "arm-trusted-firmware";
277 description = "ARM Trusted Firmware";
281 The same applies for OP-TEE if that is available.
283 If each binary is not available, the relevant template node (@atf-SEQ or
284 @tee-SEQ) is removed from the output.
286 This also generates a `config-xxx` node for each device tree in `of-list`.
287 Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
288 so you can use `CONFIG_OF_LIST` to define that list. In this example it is
289 set up for `firefly-rk3399` with a single device tree and the default set
290 with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
294 default = "config-1";
296 loadables = "atf-1", "atf-2", "atf-3", "tee-1", "tee-2";
297 description = "rk3399-firefly.dtb";
303 U-Boot SPL can then load the firmware (U-Boot proper) and all the loadables
304 (ATF and TEE), then proceed with the boot.
306 def __init__(self, section, etype, node):
309 _fit: FIT file being built
310 _entries: dict from Entry_section:
311 key: relative path to entry Node (from the base of the FIT)
312 value: Entry_section object comprising the contents of this
314 _priv_entries: Internal copy of _entries which includes 'generator'
315 entries which are used to create the FIT, but should not be
316 processed as real entries. This is set up once we have the
318 _loadables: List of generated split-elf nodes, each a node name
320 super().__init__(section, etype, node)
325 self._priv_entries = {}
330 for pname, prop in self._node.props.items():
331 if pname.startswith('fit,'):
332 self._fit_props[pname] = prop
333 self._fit_list_prop = self._fit_props.get('fit,fdt-list')
334 if self._fit_list_prop:
335 fdts, = self.GetEntryArgsOrProps(
336 [EntryArg(self._fit_list_prop.value, str)])
338 self._fdts = fdts.split()
339 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
342 def _get_operation(self, base_node, node):
343 """Get the operation referenced by a subnode
346 node (Node): Subnode (of the FIT) to check
349 int: Operation to perform
352 ValueError: Invalid operation name
354 oper_name = node.props.get('fit,operation')
356 return OP_GEN_FDT_NODES
357 oper = OPERATIONS.get(oper_name.value)
359 self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
362 def ReadEntries(self):
363 def _add_entries(base_node, depth, node):
364 """Add entries for any nodes that need them
367 base_node: Base Node of the FIT (with 'description' property)
368 depth: Current node depth (0 is the base 'fit' node)
369 node: Current node to process
371 Here we only need to provide binman entries which are used to define
372 the 'data' for each image. We create an entry_Section for each.
374 rel_path = node.path[len(base_node.path):]
375 in_images = rel_path.startswith('/images')
376 has_images = depth == 2 and in_images
378 # This node is a FIT subimage node (e.g. "/images/kernel")
379 # containing content nodes. We collect the subimage nodes and
380 # section entries for them here to merge the content subnodes
381 # together and put the merged contents in the subimage node's
382 # 'data' property later.
383 entry = Entry.Create(self.section, node, etype='section')
385 # The hash subnodes here are for mkimage, not binman.
386 entry.SetUpdateHash(False)
387 self._entries[rel_path] = entry
389 for subnode in node.subnodes:
390 _add_entries(base_node, depth + 1, subnode)
392 _add_entries(self._node, 0, self._node)
394 # Keep a copy of all entries, including generator entries, since these
395 # removed from self._entries later.
396 self._priv_entries = dict(self._entries)
398 def BuildSectionData(self, required):
399 """Build FIT entry contents
401 This adds the 'data' properties to the input ITB (Image-tree Binary)
402 then runs mkimage to process it.
405 required (bool): True if the data must be present, False if it is OK
409 bytes: Contents of the section
411 data = self._build_input()
412 uniq = self.GetUniqueName()
413 input_fname = tools.get_output_filename(f'{uniq}.itb')
414 output_fname = tools.get_output_filename(f'{uniq}.fit')
415 tools.write_file(input_fname, data)
416 tools.write_file(output_fname, data)
419 ext_offset = self._fit_props.get('fit,external-offset')
420 if ext_offset is not None:
423 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
425 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
427 # Bintool is missing; just use empty data as the output
428 self.record_missing_bintool(self.mkimage)
429 return tools.get_bytes(0, 1024)
431 return tools.read_file(output_fname)
433 def _raise_subnode(self, node, msg):
434 """Raise an error with a paticular FIT subnode
437 node (Node): FIT subnode containing the error
438 msg (str): Message to report
441 ValueError, as requested
443 rel_path = node.path[len(self._node.path) + 1:]
444 self.Raise(f"subnode '{rel_path}': {msg}")
446 def _build_input(self):
447 """Finish the FIT by adding the 'data' properties to it
453 bytes: New fdt contents
455 def _process_prop(pname, prop):
456 """Process special properties
458 Handles properties with generated values. At present the only
459 supported property is 'default', i.e. the default device tree in
460 the configurations node.
463 pname (str): Name of property
464 prop (Prop): Property to process
466 if pname == 'default':
468 # Handle the 'default' property
469 if val.startswith('@'):
472 if not self._fit_default_dt:
473 self.Raise("Generated 'default' node requires default-dt entry argument")
474 if self._fit_default_dt not in self._fdts:
476 f"default-dt entry argument '{self._fit_default_dt}' "
477 f"not found in fdt list: {', '.join(self._fdts)}")
478 seq = self._fdts.index(self._fit_default_dt)
479 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
480 fsw.property_string(pname, val)
482 elif pname.startswith('fit,'):
483 # Ignore these, which are commands for binman to process
485 elif pname in ['offset', 'size', 'image-pos']:
486 # Don't add binman's calculated properties
488 fsw.property(pname, prop.bytes)
490 def _gen_fdt_nodes(base_node, node, depth, in_images):
491 """Generate FDT nodes
493 This creates one node for each member of self._fdts using the
494 provided template. If a property value contains 'NAME' it is
495 replaced with the filename of the FDT. If a property value contains
496 SEQ it is replaced with the node sequence number, where 1 is the
500 node (None): Generator node to process
501 depth: Current node depth (0 is the base 'fit' node)
502 in_images: True if this is inside the 'images' node, so that
503 'data' properties should be generated
506 # Generate nodes for each FDT
507 for seq, fdt_fname in enumerate(self._fdts):
508 node_name = node.name[1:].replace('SEQ', str(seq + 1))
509 fname = tools.get_input_filename(fdt_fname + '.dtb')
510 with fsw.add_node(node_name):
511 for pname, prop in node.props.items():
512 if pname == 'fit,loadables':
513 val = '\0'.join(self._loadables) + '\0'
514 fsw.property('loadables', val.encode('utf-8'))
515 elif pname == 'fit,operation':
517 elif pname.startswith('fit,'):
519 node, f"Unknown directive '{pname}'")
521 val = prop.bytes.replace(
522 b'NAME', tools.to_bytes(fdt_fname))
524 b'SEQ', tools.to_bytes(str(seq + 1)))
525 fsw.property(pname, val)
527 # Add data for 'images' nodes (but not 'config')
528 if depth == 1 and in_images:
529 fsw.property('data', tools.read_file(fname))
531 for subnode in node.subnodes:
532 with fsw.add_node(subnode.name):
533 _add_node(node, depth + 1, subnode)
535 if self._fdts is None:
536 if self._fit_list_prop:
537 self.Raise('Generator node requires '
538 f"'{self._fit_list_prop.value}' entry argument")
540 self.Raise("Generator node requires 'fit,fdt-list' property")
542 def _gen_split_elf(base_node, node, elf_data, missing):
543 """Add nodes for the ELF file, one per group of contiguous segments
546 base_node (Node): Template node from the binman definition
547 node (Node): Node to replace (in the FIT being built)
548 data (bytes): ELF-format data to process (may be empty)
549 missing (bool): True if any of the data is missing
552 # If any pieces are missing, skip this. The missing entries will
556 segments, entry = elf.read_loadable_segments(elf_data)
557 except ValueError as exc:
558 self._raise_subnode(node,
559 f'Failed to read ELF file: {str(exc)}')
560 for (seq, start, data) in segments:
561 node_name = node.name[1:].replace('SEQ', str(seq + 1))
562 with fsw.add_node(node_name):
563 loadables.append(node_name)
564 for pname, prop in node.props.items():
565 if not pname.startswith('fit,'):
566 fsw.property(pname, prop.bytes)
567 elif pname == 'fit,load':
568 fsw.property_u32('load', start)
569 elif pname == 'fit,entry':
571 fsw.property_u32('entry', entry)
572 elif pname == 'fit,data':
573 fsw.property('data', bytes(data))
574 elif pname != 'fit,operation':
576 node, f"Unknown directive '{pname}'")
578 def _gen_node(base_node, node, depth, in_images, entry):
579 """Generate nodes from a template
581 This creates one node for each member of self._fdts using the
582 provided template. If a property value contains 'NAME' it is
583 replaced with the filename of the FDT. If a property value contains
584 SEQ it is replaced with the node sequence number, where 1 is the
588 base_node (Node): Base Node of the FIT (with 'description'
590 node (Node): Generator node to process
591 depth (int): Current node depth (0 is the base 'fit' node)
592 in_images (bool): True if this is inside the 'images' node, so
593 that 'data' properties should be generated
595 oper = self._get_operation(base_node, node)
596 if oper == OP_GEN_FDT_NODES:
597 _gen_fdt_nodes(base_node, node, depth, in_images)
598 elif oper == OP_SPLIT_ELF:
599 # Entry_section.ObtainContents() either returns True or
600 # raises an exception.
603 entry.ObtainContents()
605 data = entry.GetData()
606 entry.CheckMissing(missing_list)
608 _gen_split_elf(base_node, node, data, bool(missing_list))
610 def _add_node(base_node, depth, node):
611 """Add nodes to the output FIT
614 base_node (Node): Base Node of the FIT (with 'description'
616 depth (int): Current node depth (0 is the base 'fit' node)
617 node (Node): Current node to process
619 There are two cases to deal with:
620 - hash and signature nodes which become part of the FIT
621 - binman entries which are used to define the 'data' for each
622 image, so don't appear in the FIT
624 # Copy over all the relevant properties
625 for pname, prop in node.props.items():
626 _process_prop(pname, prop)
628 rel_path = node.path[len(base_node.path):]
629 in_images = rel_path.startswith('/images')
631 has_images = depth == 2 and in_images
633 entry = self._priv_entries[rel_path]
634 data = entry.GetData()
635 fsw.property('data', bytes(data))
637 for subnode in node.subnodes:
638 subnode_path = f'{rel_path}/{subnode.name}'
639 if has_images and not (subnode.name.startswith('hash') or
640 subnode.name.startswith('signature')):
641 # This subnode is a content node not meant to appear in
642 # the FIT (e.g. "/images/kernel/u-boot"), so don't call
643 # fsw.add_node() or _add_node() for it.
645 elif self.GetImage().generate and subnode.name.startswith('@'):
646 entry = self._priv_entries.get(subnode_path)
647 _gen_node(base_node, subnode, depth, in_images, entry)
648 # This is a generator (template) entry, so remove it from
649 # the list of entries used by PackEntries(), etc. Otherwise
650 # it will appear in the binman output
651 to_remove.append(subnode_path)
653 with fsw.add_node(subnode.name):
654 _add_node(base_node, depth + 1, subnode)
656 # Build a new tree with all nodes and properties starting from the
659 fsw.finish_reservemap()
662 with fsw.add_node(''):
663 _add_node(self._node, 0, self._node)
664 self._loadables = loadables
667 # Remove generator entries from the main list
668 for path in to_remove:
669 if path in self._entries:
670 del self._entries[path]
672 # Pack this new FDT and scan it so we can add the data later
674 data = fdt.as_bytearray()
677 def SetImagePos(self, image_pos):
678 """Set the position in the image
680 This sets each subentry's offsets, sizes and positions-in-image
681 according to where they ended up in the packed FIT file.
684 image_pos (int): Position of this entry in the image
686 super().SetImagePos(image_pos)
688 # If mkimage is missing we'll have empty data,
689 # which will cause a FDT_ERR_BADMAGIC error
690 if self.mkimage in self.missing_bintools:
693 fdt = Fdt.FromData(self.GetData())
696 for path, section in self._entries.items():
697 node = fdt.GetNode(path)
699 data_prop = node.props.get("data")
700 data_pos = fdt_util.GetInt(node, "data-position")
701 data_offset = fdt_util.GetInt(node, "data-offset")
702 data_size = fdt_util.GetInt(node, "data-size")
704 # Contents are inside the FIT
705 if data_prop is not None:
706 # GetOffset() returns offset of a fdt_property struct,
707 # which has 3 fdt32_t members before the actual data.
708 offset = data_prop.GetOffset() + 12
709 size = len(data_prop.bytes)
711 # External offset from the base of the FIT
712 elif data_pos is not None:
716 # External offset from the end of the FIT, not used in binman
717 elif data_offset is not None: # pragma: no cover
718 offset = fdt.GetFdtObj().totalsize() + data_offset
721 # This should never happen
722 else: # pragma: no cover
723 self.Raise(f'{path}: missing data properties')
725 section.SetOffsetSize(offset, size)
726 section.SetImagePos(self.image_pos)
728 def AddBintools(self, btools):
729 super().AddBintools(btools)
730 self.mkimage = self.AddBintool(btools, 'mkimage')
732 def CheckMissing(self, missing_list):
733 # We must use our private entry list for this since generator notes
734 # which are removed from self._entries will otherwise not show up as
736 for entry in self._priv_entries.values():
737 entry.CheckMissing(missing_list)