1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
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_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, node, etype='section')
385 # The hash subnodes here are for mkimage, not binman.
386 entry.SetUpdateHash(False)
387 image_name = rel_path[len('/images/'):]
388 self._entries[image_name] = entry
390 for subnode in node.subnodes:
391 _add_entries(base_node, depth + 1, subnode)
393 _add_entries(self._node, 0, self._node)
395 # Keep a copy of all entries, including generator entries, since these
396 # removed from self._entries later.
397 self._priv_entries = dict(self._entries)
399 def BuildSectionData(self, required):
400 """Build FIT entry contents
402 This adds the 'data' properties to the input ITB (Image-tree Binary)
403 then runs mkimage to process it.
406 required (bool): True if the data must be present, False if it is OK
410 bytes: Contents of the section
412 data = self._build_input()
413 uniq = self.GetUniqueName()
414 input_fname = tools.get_output_filename(f'{uniq}.itb')
415 output_fname = tools.get_output_filename(f'{uniq}.fit')
416 tools.write_file(input_fname, data)
417 tools.write_file(output_fname, data)
420 ext_offset = self._fit_props.get('fit,external-offset')
421 if ext_offset is not None:
424 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
426 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
428 # Bintool is missing; just use empty data as the output
429 self.record_missing_bintool(self.mkimage)
430 return tools.get_bytes(0, 1024)
432 return tools.read_file(output_fname)
434 def _raise_subnode(self, node, msg):
435 """Raise an error with a paticular FIT subnode
438 node (Node): FIT subnode containing the error
439 msg (str): Message to report
442 ValueError, as requested
444 rel_path = node.path[len(self._node.path) + 1:]
445 self.Raise(f"subnode '{rel_path}': {msg}")
447 def _build_input(self):
448 """Finish the FIT by adding the 'data' properties to it
454 bytes: New fdt contents
456 def _process_prop(pname, prop):
457 """Process special properties
459 Handles properties with generated values. At present the only
460 supported property is 'default', i.e. the default device tree in
461 the configurations node.
464 pname (str): Name of property
465 prop (Prop): Property to process
467 if pname == 'default':
469 # Handle the 'default' property
470 if val.startswith('@'):
473 if not self._fit_default_dt:
474 self.Raise("Generated 'default' node requires default-dt entry argument")
475 if self._fit_default_dt not in self._fdts:
477 f"default-dt entry argument '{self._fit_default_dt}' "
478 f"not found in fdt list: {', '.join(self._fdts)}")
479 seq = self._fdts.index(self._fit_default_dt)
480 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
481 fsw.property_string(pname, val)
483 elif pname.startswith('fit,'):
484 # Ignore these, which are commands for binman to process
486 elif pname in ['offset', 'size', 'image-pos']:
487 # Don't add binman's calculated properties
489 fsw.property(pname, prop.bytes)
491 def _gen_fdt_nodes(base_node, node, depth, in_images):
492 """Generate FDT nodes
494 This creates one node for each member of self._fdts using the
495 provided template. If a property value contains 'NAME' it is
496 replaced with the filename of the FDT. If a property value contains
497 SEQ it is replaced with the node sequence number, where 1 is the
501 node (None): Generator node to process
502 depth: Current node depth (0 is the base 'fit' node)
503 in_images: True if this is inside the 'images' node, so that
504 'data' properties should be generated
507 # Generate nodes for each FDT
508 for seq, fdt_fname in enumerate(self._fdts):
509 node_name = node.name[1:].replace('SEQ', str(seq + 1))
510 fname = tools.get_input_filename(fdt_fname + '.dtb')
511 with fsw.add_node(node_name):
512 for pname, prop in node.props.items():
513 if pname == 'fit,loadables':
514 val = '\0'.join(self._loadables) + '\0'
515 fsw.property('loadables', val.encode('utf-8'))
516 elif pname == 'fit,operation':
518 elif pname.startswith('fit,'):
520 node, f"Unknown directive '{pname}'")
522 val = prop.bytes.replace(
523 b'NAME', tools.to_bytes(fdt_fname))
525 b'SEQ', tools.to_bytes(str(seq + 1)))
526 fsw.property(pname, val)
528 # Add data for 'images' nodes (but not 'config')
529 if depth == 1 and in_images:
530 fsw.property('data', tools.read_file(fname))
532 for subnode in node.subnodes:
533 with fsw.add_node(subnode.name):
534 _add_node(node, depth + 1, subnode)
536 if self._fdts is None:
537 if self._fit_list_prop:
538 self.Raise('Generator node requires '
539 f"'{self._fit_list_prop.value}' entry argument")
541 self.Raise("Generator node requires 'fit,fdt-list' property")
543 def _gen_split_elf(base_node, node, elf_data, missing):
544 """Add nodes for the ELF file, one per group of contiguous segments
547 base_node (Node): Template node from the binman definition
548 node (Node): Node to replace (in the FIT being built)
549 data (bytes): ELF-format data to process (may be empty)
550 missing (bool): True if any of the data is missing
553 # If any pieces are missing, skip this. The missing entries will
557 segments, entry = elf.read_loadable_segments(elf_data)
558 except ValueError as exc:
559 self._raise_subnode(node,
560 f'Failed to read ELF file: {str(exc)}')
561 for (seq, start, data) in segments:
562 node_name = node.name[1:].replace('SEQ', str(seq + 1))
563 with fsw.add_node(node_name):
564 loadables.append(node_name)
565 for pname, prop in node.props.items():
566 if not pname.startswith('fit,'):
567 fsw.property(pname, prop.bytes)
568 elif pname == 'fit,load':
569 fsw.property_u32('load', start)
570 elif pname == 'fit,entry':
572 fsw.property_u32('entry', entry)
573 elif pname == 'fit,data':
574 fsw.property('data', bytes(data))
575 elif pname != 'fit,operation':
577 node, f"Unknown directive '{pname}'")
579 def _gen_node(base_node, node, depth, in_images, entry):
580 """Generate nodes from a template
582 This creates one node for each member of self._fdts using the
583 provided template. If a property value contains 'NAME' it is
584 replaced with the filename of the FDT. If a property value contains
585 SEQ it is replaced with the node sequence number, where 1 is the
589 base_node (Node): Base Node of the FIT (with 'description'
591 node (Node): Generator node to process
592 depth (int): Current node depth (0 is the base 'fit' node)
593 in_images (bool): True if this is inside the 'images' node, so
594 that 'data' properties should be generated
596 oper = self._get_operation(base_node, node)
597 if oper == OP_GEN_FDT_NODES:
598 _gen_fdt_nodes(base_node, node, depth, in_images)
599 elif oper == OP_SPLIT_ELF:
600 # Entry_section.ObtainContents() either returns True or
601 # raises an exception.
604 entry.ObtainContents()
606 data = entry.GetData()
607 entry.CheckMissing(missing_list)
609 _gen_split_elf(base_node, node, data, bool(missing_list))
611 def _add_node(base_node, depth, node):
612 """Add nodes to the output FIT
615 base_node (Node): Base Node of the FIT (with 'description'
617 depth (int): Current node depth (0 is the base 'fit' node)
618 node (Node): Current node to process
620 There are two cases to deal with:
621 - hash and signature nodes which become part of the FIT
622 - binman entries which are used to define the 'data' for each
623 image, so don't appear in the FIT
625 # Copy over all the relevant properties
626 for pname, prop in node.props.items():
627 _process_prop(pname, prop)
629 rel_path = node.path[len(base_node.path):]
630 in_images = rel_path.startswith('/images')
632 has_images = depth == 2 and in_images
634 image_name = rel_path[len('/images/'):]
635 entry = self._priv_entries[image_name]
636 data = entry.GetData()
637 fsw.property('data', bytes(data))
639 for subnode in node.subnodes:
640 subnode_path = f'{rel_path}/{subnode.name}'
641 if has_images and not (subnode.name.startswith('hash') or
642 subnode.name.startswith('signature')):
643 # This subnode is a content node not meant to appear in
644 # the FIT (e.g. "/images/kernel/u-boot"), so don't call
645 # fsw.add_node() or _add_node() for it.
647 elif self.GetImage().generate and subnode.name.startswith('@'):
648 entry = self._priv_entries.get(subnode.name)
649 _gen_node(base_node, subnode, depth, in_images, entry)
650 # This is a generator (template) entry, so remove it from
651 # the list of entries used by PackEntries(), etc. Otherwise
652 # it will appear in the binman output
653 to_remove.append(subnode.name)
655 with fsw.add_node(subnode.name):
656 _add_node(base_node, depth + 1, subnode)
658 # Build a new tree with all nodes and properties starting from the
662 fsw.finish_reservemap()
665 with fsw.add_node(''):
666 _add_node(self._node, 0, self._node)
667 self._loadables = loadables
670 # Remove generator entries from the main list
671 for path in to_remove:
672 if path in self._entries:
673 del self._entries[path]
675 # Pack this new FDT and scan it so we can add the data later
677 data = fdt.as_bytearray()
680 def SetImagePos(self, image_pos):
681 """Set the position in the image
683 This sets each subentry's offsets, sizes and positions-in-image
684 according to where they ended up in the packed FIT file.
687 image_pos (int): Position of this entry in the image
689 super().SetImagePos(image_pos)
691 # If mkimage is missing we'll have empty data,
692 # which will cause a FDT_ERR_BADMAGIC error
693 if self.mkimage in self.missing_bintools:
696 fdt = Fdt.FromData(self.GetData())
699 for image_name, section in self._entries.items():
700 path = f"/images/{image_name}"
701 node = fdt.GetNode(path)
703 data_prop = node.props.get("data")
704 data_pos = fdt_util.GetInt(node, "data-position")
705 data_offset = fdt_util.GetInt(node, "data-offset")
706 data_size = fdt_util.GetInt(node, "data-size")
708 # Contents are inside the FIT
709 if data_prop is not None:
710 # GetOffset() returns offset of a fdt_property struct,
711 # which has 3 fdt32_t members before the actual data.
712 offset = data_prop.GetOffset() + 12
713 size = len(data_prop.bytes)
715 # External offset from the base of the FIT
716 elif data_pos is not None:
720 # External offset from the end of the FIT, not used in binman
721 elif data_offset is not None: # pragma: no cover
722 offset = fdt.GetFdtObj().totalsize() + data_offset
725 # This should never happen
726 else: # pragma: no cover
727 self.Raise(f'{path}: missing data properties')
729 section.SetOffsetSize(offset, size)
730 section.SetImagePos(self.image_pos)
732 def AddBintools(self, btools):
733 super().AddBintools(btools)
734 self.mkimage = self.AddBintool(btools, 'mkimage')
736 def CheckMissing(self, missing_list):
737 # We must use our private entry list for this since generator notes
738 # which are removed from self._entries will otherwise not show up as
740 for entry in self._priv_entries.values():
741 entry.CheckMissing(missing_list)