1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5
6"""Entry-type module for producing a FIT"""
7
8import glob
9import os
10
11import libfdt
12import os
13
14from binman.entry import Entry, EntryArg
15from binman.etype.section import Entry_section
16from binman import elf
17from dtoc import fdt_util
18from dtoc.fdt import Fdt
19from u_boot_pylib import tools
20
21# Supported operations, with the fit,operation property
22OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
23OPERATIONS = {
24    'gen-fdt-nodes': OP_GEN_FDT_NODES,
25    'split-elf': OP_SPLIT_ELF,
26    }
27
28# pylint: disable=invalid-name
29class Entry_fit(Entry_section):
30
31    """Flat Image Tree (FIT)
32
33    This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
34    input provided.
35
36    Nodes for the FIT should be written out in the binman configuration just as
37    they would be in a file passed to mkimage.
38
39    For example, this creates an image containing a FIT with U-Boot SPL::
40
41        binman {
42            fit {
43                description = "Test FIT";
44                fit,fdt-list = "of-list";
45
46                images {
47                    kernel@1 {
48                        description = "SPL";
49                        os = "u-boot";
50                        type = "rkspi";
51                        arch = "arm";
52                        compression = "none";
53                        load = <0>;
54                        entry = <0>;
55
56                        u-boot-spl {
57                        };
58                    };
59                };
60            };
61        };
62
63    More complex setups can be created, with generated nodes, as described
64    below.
65
66    Properties (in the 'fit' node itself)
67    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68
69    Special properties have a `fit,` prefix, indicating that they should be
70    processed but not included in the final FIT.
71
72    The top-level 'fit' node supports the following special properties:
73
74        fit,external-offset
75            Indicates that the contents of the FIT are external and provides the
76            external offset. This is passed to mkimage via the -E and -p flags.
77
78        fit,align
79            Indicates what alignment to use for the FIT and its external data,
80            and provides the alignment to use. This is passed to mkimage via
81            the -B flag.
82
83        fit,fdt-list
84            Indicates the entry argument which provides the list of device tree
85            files for the gen-fdt-nodes operation (as below). This is often
86            `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
87            to binman.
88
89        fit,fdt-list-val
90            As an alternative to fit,fdt-list the list of device tree files
91            can be provided in this property as a string list, e.g.::
92
93                fit,fdt-list-val = "dtb1", "dtb2";
94
95        fit,fdt-list-dir
96            As an alternative to fit,fdt-list the list of device tree files
97            can be provided as a directory. Each .dtb file in the directory is
98            processed, , e.g.::
99
100                fit,fdt-list-dir = "arch/arm/dts";
101
102            In this case the input directories are ignored and all devicetree
103            files must be in that directory.
104
105        fit,sign
106            Enable signing FIT images via mkimage as described in
107            verified-boot.rst. If the property is found, the private keys path
108            is detected among binman include directories and passed to mkimage
109            via  -k flag. All the keys required for signing FIT must be
110            available at time of signing and must be located in single include
111            directory.
112
113        fit,encrypt
114            Enable data encryption in FIT images via mkimage. If the property
115            is found, the keys path is detected among binman include
116            directories and passed to mkimage via  -k flag. All the keys
117            required for encrypting the FIT must be available at the time of
118            encrypting and must be located in a single include directory.
119
120    Substitutions
121    ~~~~~~~~~~~~~
122
123    Node names and property values support a basic string-substitution feature.
124    Available substitutions for '@' nodes (and property values) are:
125
126    SEQ:
127        Sequence number of the generated fdt (1, 2, ...)
128    NAME
129        Name of the dtb as provided (i.e. without adding '.dtb')
130
131    The `default` property, if present, will be automatically set to the name
132    if of configuration whose devicetree matches the `default-dt` entry
133    argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
134
135    Available substitutions for property values in these nodes are:
136
137    DEFAULT-SEQ:
138        Sequence number of the default fdt, as provided by the 'default-dt'
139        entry argument
140
141    DEFAULT-NAME:
142        Name of the default fdt, as provided by the 'default-dt' entry argument
143
144    Available operations
145    ~~~~~~~~~~~~~~~~~~~~
146
147    You can add an operation to an '@' node to indicate which operation is
148    required::
149
150        @fdt-SEQ {
151            fit,operation = "gen-fdt-nodes";
152            ...
153        };
154
155    Available operations are:
156
157    gen-fdt-nodes
158        Generate FDT nodes as above. This is the default if there is no
159        `fit,operation` property.
160
161    split-elf
162        Split an ELF file into a separate node for each segment.
163
164    Generating nodes from an FDT list (gen-fdt-nodes)
165    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
166
167    U-Boot supports creating fdt and config nodes automatically. To do this,
168    pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
169    binman that you want to generates nodes for two files: `file1.dtb` and
170    `file2.dtb`. The `fit,fdt-list` property (see above) indicates that
171    `of-list` should be used. If the property is missing you will get an error.
172
173    Then add a 'generator node', a node with a name starting with '@'::
174
175        images {
176            @fdt-SEQ {
177                description = "fdt-NAME";
178                type = "flat_dt";
179                compression = "none";
180            };
181        };
182
183    This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
184    files. All the properties you specify will be included in the node. This
185    node acts like a template to generate the nodes. The generator node itself
186    does not appear in the output - it is replaced with what binman generates.
187    A 'data' property is created with the contents of the FDT file.
188
189    You can create config nodes in a similar way::
190
191        configurations {
192            default = "@config-DEFAULT-SEQ";
193            @config-SEQ {
194                description = "NAME";
195                firmware = "atf";
196                loadables = "uboot";
197                fdt = "fdt-SEQ";
198                fit,compatible;    // optional
199            };
200        };
201
202    This tells binman to create nodes `config-1` and `config-2`, i.e. a config
203    for each of your two files.
204
205    It is also possible to use NAME in the node names so that the FDT files name
206    will be used instead of the sequence number. This can be useful to identify
207    easily at runtime in U-Boot, the config to be used::
208
209        configurations {
210            default = "@config-DEFAULT-NAME";
211            @config-NAME {
212                description = "NAME";
213                firmware = "atf";
214                loadables = "uboot";
215                fdt = "fdt-NAME";
216                fit,compatible;    // optional
217            };
218        };
219
220    Note that if no devicetree files are provided (with '-a of-list' as above)
221    then no nodes will be generated.
222
223    The 'fit,compatible' property (if present) is replaced with the compatible
224    string from the root node of the devicetree, so that things work correctly
225    with FIT's configuration-matching algortihm.
226
227    Dealing with phases
228    ~~~~~~~~~~~~~~~~~~~
229
230    FIT can be used to load firmware. In this case it may be necessary to run
231    the devicetree for each model through fdtgrep to remove unwanted properties.
232    The 'fit,fdt-phase' property can be provided to indicate the phase for which
233    the devicetree is intended.
234
235    For example this indicates that the FDT should be processed for VPL::
236
237        images {
238            @fdt-SEQ {
239                description = "fdt-NAME";
240                type = "flat_dt";
241                compression = "none";
242                fit,fdt-phase = "vpl";
243            };
244        };
245
246    Using this mechanism, it is possible to generate a FIT which can provide VPL
247    images for multiple models, with TPL selecting the correct model to use. The
248    same approach can of course be used for SPL images.
249
250    Note that the `of-spl-remove-props` entryarg can be used to indicate
251    additional properties to remove. It is often used to remove properties like
252    `clock-names` and `pinctrl-names` which are not needed in SPL builds. This
253    value is automatically passed to binman by the U-Boot build.
254
255    See :ref:`fdtgrep_filter` for more information.
256
257    Generating nodes from an ELF file (split-elf)
258    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
259
260    This uses the node as a template to generate multiple nodes. The following
261    special properties are available:
262
263    split-elf
264        Split an ELF file into a separate node for each segment. This uses the
265        node as a template to generate multiple nodes. The following special
266        properties are available:
267
268        fit,load
269            Generates a `load = <...>` property with the load address of the
270            segment
271
272        fit,entry
273            Generates a `entry = <...>` property with the entry address of the
274            ELF. This is only produced for the first entry
275
276        fit,data
277            Generates a `data = <...>` property with the contents of the segment
278
279        fit,firmware
280            Generates a `firmware = <...>` property. Provides a list of possible
281            nodes to be used as the `firmware` property value. The first valid
282            node is picked as the firmware. Any remaining valid nodes is
283            prepended to the `loadable` property generated by `fit,loadables`
284
285        fit,loadables
286            Generates a `loadable = <...>` property with a list of the generated
287            nodes (including all nodes if this operation is used multiple times)
288
289
290    Here is an example showing ATF, TEE and a device tree all combined::
291
292        fit {
293            description = "test-desc";
294            #address-cells = <1>;
295            fit,fdt-list = "of-list";
296
297            images {
298                u-boot {
299                    description = "U-Boot (64-bit)";
300                    type = "standalone";
301                    os = "U-Boot";
302                    arch = "arm64";
303                    compression = "none";
304                    load = <CONFIG_TEXT_BASE>;
305                    u-boot-nodtb {
306                    };
307                };
308                @fdt-SEQ {
309                    description = "fdt-NAME.dtb";
310                    type = "flat_dt";
311                    compression = "none";
312                };
313                @atf-SEQ {
314                    fit,operation = "split-elf";
315                    description = "ARM Trusted Firmware";
316                    type = "firmware";
317                    arch = "arm64";
318                    os = "arm-trusted-firmware";
319                    compression = "none";
320                    fit,load;
321                    fit,entry;
322                    fit,data;
323
324                    atf-bl31 {
325                    };
326                    hash {
327                        algo = "sha256";
328                    };
329                };
330
331                @tee-SEQ {
332                    fit,operation = "split-elf";
333                    description = "TEE";
334                    type = "tee";
335                    arch = "arm64";
336                    os = "tee";
337                    compression = "none";
338                    fit,load;
339                    fit,entry;
340                    fit,data;
341
342                    tee-os {
343                    };
344                    hash {
345                        algo = "sha256";
346                    };
347                };
348            };
349
350            configurations {
351                default = "@config-DEFAULT-SEQ";
352                @config-SEQ {
353                    description = "conf-NAME.dtb";
354                    fdt = "fdt-SEQ";
355                    fit,firmware = "atf-1", "u-boot";
356                    fit,loadables;
357                };
358            };
359        };
360
361    If ATF-BL31 is available, this generates a node for each segment in the
362    ELF file, for example::
363
364        images {
365            atf-1 {
366                data = <...contents of first segment...>;
367                data-offset = <0x00000000>;
368                entry = <0x00040000>;
369                load = <0x00040000>;
370                compression = "none";
371                os = "arm-trusted-firmware";
372                arch = "arm64";
373                type = "firmware";
374                description = "ARM Trusted Firmware";
375                hash {
376                    algo = "sha256";
377                    value = <...hash of first segment...>;
378                };
379            };
380            atf-2 {
381                data = <...contents of second segment...>;
382                load = <0xff3b0000>;
383                compression = "none";
384                os = "arm-trusted-firmware";
385                arch = "arm64";
386                type = "firmware";
387                description = "ARM Trusted Firmware";
388                hash {
389                    algo = "sha256";
390                    value = <...hash of second segment...>;
391                };
392            };
393        };
394
395    The same applies for OP-TEE if that is available.
396
397    If each binary is not available, the relevant template node (@atf-SEQ or
398    @tee-SEQ) is removed from the output.
399
400    This also generates a `config-xxx` node for each device tree in `of-list`.
401    Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)`
402    so you can use `CONFIG_OF_LIST` to define that list. In this example it is
403    set up for `firefly-rk3399` with a single device tree and the default set
404    with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output
405    is::
406
407        configurations {
408            default = "config-1";
409            config-1 {
410                loadables = "u-boot", "atf-2", "atf-3", "tee-1", "tee-2";
411                description = "rk3399-firefly.dtb";
412                fdt = "fdt-1";
413                firmware = "atf-1";
414            };
415        };
416
417    U-Boot SPL can then load the firmware (ATF) and all the loadables (U-Boot
418    proper, ATF and TEE), then proceed with the boot.
419    """
420    def __init__(self, section, etype, node):
421        """
422        Members:
423            _fit (str): FIT file being built
424            _fit_props (list of str): 'fit,...' properties found in the
425                top-level node
426            _fdts (list of str): Filenames of .dtb files to process
427            _fdt_dir (str): Directory to scan to find .dtb files, or None
428            _fit_list_prop (str): Name of the EntryArg containing a list of .dtb
429                files
430            _fit_default_dt (str): Name of the EntryArg containing the default
431                .dtb file
432            _entries (dict of entries): from Entry_section:
433                key: relative path to entry Node (from the base of the FIT)
434                value: Entry_section object comprising the contents of this
435                    node
436            _priv_entries (dict of entries): Internal copy of _entries which
437                includes 'generator' entries which are used to create the FIT,
438                but should not be processed as real entries. This is set up once
439                we have the entries
440            _loadables (list of str): List of generated split-elf nodes, each
441                a node name
442            _remove_props (list of str): Value of of-spl-remove-props EntryArg,
443                the list of properties to remove with fdtgrep
444            mkimage (Bintool): mkimage tool
445            fdtgrep (Bintool): fdtgrep tool
446        """
447        super().__init__(section, etype, node)
448        self._fit = None
449        self._fit_props = {}
450        self._fdts = None
451        self._fdt_dir = None
452        self._fit_list_prop = None
453        self._fit_default_dt = None
454        self._priv_entries = {}
455        self._loadables = []
456        self._remove_props = []
457        props = self.GetEntryArgsOrProps(
458            [EntryArg('of-spl-remove-props', str)], required=False)[0]
459        if props:
460            self._remove_props = props.split()
461        self.mkimage = None
462        self.fdtgrep = None
463        self._fit_sign = None
464
465    def ReadNode(self):
466        super().ReadNode()
467        for pname, prop in self._node.props.items():
468            if pname.startswith('fit,'):
469                self._fit_props[pname] = prop
470        self._fit_list_prop = self._fit_props.get('fit,fdt-list')
471        if self._fit_list_prop:
472            fdts = self.GetEntryArgsOrProps(
473                [EntryArg(self._fit_list_prop.value, str)])[0]
474            if fdts is not None:
475                self._fdts = fdts.split()
476        else:
477            self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir')
478            if self._fdt_dir:
479                indir = tools.get_input_filename(self._fdt_dir)
480                if indir:
481                    tools.append_input_dirs(indir)
482                fdts = glob.glob('*.dtb', root_dir=indir)
483                self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)]
484            else:
485                self._fdts = fdt_util.GetStringList(self._node,
486                                                    'fit,fdt-list-val')
487
488        self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
489                                                                  str)])[0]
490
491    def _get_operation(self, node):
492        """Get the operation referenced by a subnode
493
494        Args:
495            node (Node): Subnode (of the FIT) to check
496
497        Returns:
498            int: Operation to perform
499
500        Raises:
501            ValueError: Invalid operation name
502        """
503        oper_name = node.props.get('fit,operation')
504        if not oper_name:
505            return OP_GEN_FDT_NODES
506        oper = OPERATIONS.get(oper_name.value)
507        if oper is None:
508            self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
509        return oper
510
511    def ReadEntries(self):
512        def _add_entries(base_node, depth, node):
513            """Add entries for any nodes that need them
514
515            Args:
516                base_node: Base Node of the FIT (with 'description' property)
517                depth: Current node depth (0 is the base 'fit' node)
518                node: Current node to process
519
520            Here we only need to provide binman entries which are used to define
521            the 'data' for each image. We create an entry_Section for each.
522            """
523            rel_path = node.path[len(base_node.path):]
524            in_images = rel_path.startswith('/images')
525            has_images = depth == 2 and in_images
526            if has_images:
527                # This node is a FIT subimage node (e.g. "/images/kernel")
528                # containing content nodes. We collect the subimage nodes and
529                # section entries for them here to merge the content subnodes
530                # together and put the merged contents in the subimage node's
531                # 'data' property later.
532                entry = Entry.Create(self, node, etype='section')
533                entry.ReadNode()
534                # The hash subnodes here are for mkimage, not binman.
535                entry.SetUpdateHash(False)
536                image_name = rel_path[len('/images/'):]
537                self._entries[image_name] = entry
538
539            for subnode in node.subnodes:
540                _add_entries(base_node, depth + 1, subnode)
541
542        _add_entries(self._node, 0, self._node)
543
544        # Keep a copy of all entries, including generator entries, since those
545        # are removed from self._entries later.
546        self._priv_entries = dict(self._entries)
547
548    def _get_keys_dir(self, data):
549        """Detect private and encryption keys path among binman include directories
550
551        Args:
552            data: FIT image in binary format
553
554        Returns:
555            str: Single path containing all keys found or None
556
557        Raises:
558            ValueError: Filename 'rsa2048.key' not found in input path
559            ValueError: Multiple key paths found
560            ValueError: 'dir/rsa2048' is a path not a filename
561        """
562        def _find_keys_dir(node):
563            for subnode in node.subnodes:
564                if (subnode.name.startswith('signature') or
565                    subnode.name.startswith('cipher')):
566                    hint = subnode.props['key-name-hint'].value
567                    if '/' in hint:
568                        self.Raise(f"'{hint}' is a path not a filename")
569                    name = tools.get_input_filename(
570                        f"{hint}.key" if subnode.name.startswith('signature')
571                        else f"{hint}.bin")
572                    path = os.path.dirname(name)
573                    if path not in paths:
574                        paths.append(path)
575                else:
576                    _find_keys_dir(subnode)
577            return None
578
579        fdt = Fdt.FromData(data)
580        fdt.Scan()
581
582        paths = []
583
584        _find_keys_dir(fdt.GetRoot())
585
586        if len(paths) > 1:
587            self.Raise("multiple key paths found (%s)" % ",".join(paths))
588
589        return paths[0] if len(paths) else None
590
591    def BuildSectionData(self, required):
592        """Build FIT entry contents
593
594        This adds the 'data' properties to the input ITB (Image-tree Binary)
595        then runs mkimage to process it.
596
597        Args:
598            required (bool): True if the data must be present, False if it is OK
599                to return None
600
601        Returns:
602            bytes: Contents of the section
603        """
604        data = self._build_input()
605        uniq = self.GetUniqueName()
606        input_fname = tools.get_output_filename(f'{uniq}.itb')
607        output_fname = tools.get_output_filename(f'{uniq}.fit')
608        tools.write_file(input_fname, data)
609        tools.write_file(output_fname, data)
610
611        args = {}
612        ext_offset = self._fit_props.get('fit,external-offset')
613        if ext_offset is not None:
614            args = {
615                'external': True,
616                'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
617                }
618        align = self._fit_props.get('fit,align')
619        if align is not None:
620            args.update({'align': fdt_util.fdt32_to_cpu(align.value)})
621        if (self._fit_props.get('fit,sign') is not None or
622            self._fit_props.get('fit,encrypt') is not None):
623            args.update({'keys_dir': self._get_keys_dir(data)})
624        if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
625                            **args) is None:
626            if not self.GetAllowMissing():
627                self.Raise("Missing tool: 'mkimage'")
628            # Bintool is missing; just use empty data as the output
629            self.record_missing_bintool(self.mkimage)
630            return tools.get_bytes(0, 1024)
631
632        return tools.read_file(output_fname)
633
634    def _raise_subnode(self, node, msg):
635        """Raise an error with a paticular FIT subnode
636
637        Args:
638            node (Node): FIT subnode containing the error
639            msg (str): Message to report
640
641        Raises:
642            ValueError, as requested
643        """
644        rel_path = node.path[len(self._node.path) + 1:]
645        self.Raise(f"subnode '{rel_path}': {msg}")
646
647    def _run_fdtgrep(self, infile, phase, outfile):
648        """Run fdtgrep to create the dtb for a phase
649
650        Args:
651            infile (str): Input filename containing the full FDT contents (with
652                all nodes and properties)
653            phase (str): Phase to generate for ('tpl', 'vpl', 'spl')
654            outfile (str): Output filename to write the grepped FDT contents to
655                (with only neceesary nodes and properties)
656
657        Returns:
658            str or bytes: Resulting stdout from fdtgrep
659        """
660        return self.fdtgrep.create_for_phase(infile, phase, outfile,
661                                             self._remove_props)
662
663    def _build_input(self):
664        """Finish the FIT by adding the 'data' properties to it
665
666        Returns:
667            bytes: New fdt contents
668        """
669        def _process_prop(pname, prop):
670            """Process special properties
671
672            Handles properties with generated values. At present the only
673            supported property is 'default', i.e. the default device tree in
674            the configurations node.
675
676            Args:
677                pname (str): Name of property
678                prop (Prop): Property to process
679            """
680            if pname == 'default':
681                val = prop.value
682                # Handle the 'default' property
683                if val.startswith('@'):
684                    if not self._fdts:
685                        return
686                    default_dt = self._fit_default_dt
687                    if not default_dt:
688                        self.Raise("Generated 'default' node requires default-dt entry argument")
689                    if default_dt not in self._fdts:
690                        if self._fdt_dir:
691                            default_dt = os.path.basename(default_dt)
692                        if default_dt not in self._fdts:
693                            self.Raise(
694                                f"default-dt entry argument '{self._fit_default_dt}' "
695                                f"not found in fdt list: {', '.join(self._fdts)}")
696                    seq = self._fdts.index(default_dt)
697                    val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
698                    val = val.replace('DEFAULT-NAME', self._fit_default_dt)
699                    fsw.property_string(pname, val)
700                    return
701            elif pname.startswith('fit,'):
702                # Ignore these, which are commands for binman to process
703                return
704            elif pname in ['offset', 'size', 'image-pos']:
705                # Don't add binman's calculated properties
706                return
707            fsw.property(pname, prop.bytes)
708
709        def _process_firmware_prop(node):
710            """Process optional fit,firmware property
711
712            Picks the first valid entry for use as the firmware, remaining valid
713            entries is prepended to loadables
714
715            Args:
716                node (Node): Generator node to process
717
718            Returns:
719                firmware (str): Firmware or None
720                result (list): List of remaining loadables
721            """
722            val = fdt_util.GetStringList(node, 'fit,firmware')
723            if val is None:
724                return None, loadables
725            valid_entries = list(loadables)
726            for name, entry in self.GetEntries().items():
727                missing = []
728                entry.CheckMissing(missing)
729                entry.CheckOptional(missing)
730                if not missing:
731                    valid_entries.append(name)
732            firmware = None
733            result = []
734            for name in val:
735                if name in valid_entries:
736                    if not firmware:
737                        firmware = name
738                    elif name not in result:
739                        result.append(name)
740            for name in loadables:
741                if name != firmware and name not in result:
742                    result.append(name)
743            return firmware, result
744
745        def _gen_fdt_nodes(node, depth, in_images):
746            """Generate FDT nodes
747
748            This creates one node for each member of self._fdts using the
749            provided template. If a property value contains 'NAME' it is
750            replaced with the filename of the FDT. If a property value contains
751            SEQ it is replaced with the node sequence number, where 1 is the
752            first.
753
754            Args:
755                node (Node): Generator node to process
756                depth: Current node depth (0 is the base 'fit' node)
757                in_images: True if this is inside the 'images' node, so that
758                    'data' properties should be generated
759            """
760            if self._fdts:
761                firmware, fit_loadables = _process_firmware_prop(node)
762                # Generate nodes for each FDT
763                for seq, fdt_fname in enumerate(self._fdts):
764                    node_name = node.name[1:].replace('SEQ', str(seq + 1))
765                    node_name = node_name.replace('NAME', fdt_fname)
766                    if self._fdt_dir:
767                        fname = os.path.join(self._fdt_dir, fdt_fname + '.dtb')
768                    else:
769                        fname = tools.get_input_filename(fdt_fname + '.dtb')
770                    fdt_phase = None
771                    with fsw.add_node(node_name):
772                        for pname, prop in node.props.items():
773                            if pname == 'fit,firmware':
774                                if firmware:
775                                    fsw.property_string('firmware', firmware)
776                            elif pname == 'fit,loadables':
777                                val = '\0'.join(fit_loadables) + '\0'
778                                fsw.property('loadables', val.encode('utf-8'))
779                            elif pname == 'fit,operation':
780                                pass
781                            elif pname == 'fit,compatible':
782                                fdt_phase = fdt_util.GetString(node, pname)
783                                data = tools.read_file(fname)
784                                fdt = Fdt.FromData(data)
785                                fdt.Scan()
786                                prop = fdt.GetRoot().props['compatible']
787                                fsw.property('compatible', prop.bytes)
788                            elif pname == 'fit,fdt-phase':
789                                fdt_phase = fdt_util.GetString(node, pname)
790                            elif pname.startswith('fit,'):
791                                self._raise_subnode(
792                                    node, f"Unknown directive '{pname}'")
793                            else:
794                                val = prop.bytes.replace(
795                                    b'NAME', tools.to_bytes(fdt_fname))
796                                val = val.replace(
797                                    b'SEQ', tools.to_bytes(str(seq + 1)))
798                                fsw.property(pname, val)
799
800                        # Add data for 'images' nodes (but not 'config')
801                        if depth == 1 and in_images:
802                            if fdt_phase:
803                                leaf = os.path.basename(fdt_fname)
804                                phase_fname = tools.get_output_filename(
805                                    f'{leaf}-{fdt_phase}.dtb')
806                                self._run_fdtgrep(fname, fdt_phase, phase_fname)
807                                data = tools.read_file(phase_fname)
808                            else:
809                                data = tools.read_file(fname)
810                            fsw.property('data', data)
811
812                        for subnode in node.subnodes:
813                            with fsw.add_node(subnode.name):
814                                _add_node(node, depth + 1, subnode)
815            else:
816                if self._fdts is None:
817                    if self._fit_list_prop:
818                        self.Raise('Generator node requires '
819                            f"'{self._fit_list_prop.value}' entry argument")
820                    else:
821                        self.Raise("Generator node requires 'fit,fdt-list' property")
822
823        def _gen_split_elf(node, depth, segments, entry_addr):
824            """Add nodes for the ELF file, one per group of contiguous segments
825
826            Args:
827                node (Node): Node to replace (in the FIT being built)
828                depth: Current node depth (0 is the base 'fit' node)
829                segments (list): list of segments, each:
830                    int: Segment number (0 = first)
831                    int: Start address of segment in memory
832                    bytes: Contents of segment
833                entry_addr (int): entry address of ELF file
834            """
835            for (seq, start, data) in segments:
836                node_name = node.name[1:].replace('SEQ', str(seq + 1))
837                with fsw.add_node(node_name):
838                    loadables.append(node_name)
839                    for pname, prop in node.props.items():
840                        if not pname.startswith('fit,'):
841                            fsw.property(pname, prop.bytes)
842                        elif pname == 'fit,load':
843                            fsw.property_u32('load', start)
844                        elif pname == 'fit,entry':
845                            if seq == 0:
846                                fsw.property_u32('entry', entry_addr)
847                        elif pname == 'fit,data':
848                            fsw.property('data', bytes(data))
849                        elif pname != 'fit,operation':
850                            self._raise_subnode(
851                                node, f"Unknown directive '{pname}'")
852
853                    for subnode in node.subnodes:
854                        with fsw.add_node(subnode.name):
855                            _add_node(node, depth + 1, subnode)
856
857        def _gen_node(node, depth, in_images, entry):
858            """Generate nodes from a template
859
860            This creates one or more nodes depending on the fit,operation being
861            used.
862
863            For OP_GEN_FDT_NODES it creates one node for each member of
864            self._fdts using the provided template. If a property value contains
865            'NAME' it is replaced with the filename of the FDT. If a property
866            value contains SEQ it is replaced with the node sequence number,
867            where 1 is the first.
868
869            For OP_SPLIT_ELF it emits one node for each section in the ELF file.
870            If the file is missing, nothing is generated.
871
872            Args:
873                node (Node): Generator node to process
874                depth (int): Current node depth (0 is the base 'fit' node)
875                in_images (bool): True if this is inside the 'images' node, so
876                    that 'data' properties should be generated
877                entry (entry_Section): Entry for the section containing the
878                    contents of this node
879            """
880            oper = self._get_operation(node)
881            if oper == OP_GEN_FDT_NODES:
882                _gen_fdt_nodes(node, depth, in_images)
883            elif oper == OP_SPLIT_ELF:
884                # Entry_section.ObtainContents() either returns True or
885                # raises an exception.
886                missing_opt_list = []
887                entry.ObtainContents()
888                entry.Pack(0)
889                entry.CheckMissing(missing_opt_list)
890                entry.CheckOptional(missing_opt_list)
891
892                # If any pieces are missing, skip this. The missing entries will
893                # show an error
894                if not missing_opt_list:
895                    segs = entry.read_elf_segments()
896                    if segs:
897                        segments, entry_addr = segs
898                    else:
899                        elf_data = entry.GetData()
900                        try:
901                            segments, entry_addr = (
902                                    elf.read_loadable_segments(elf_data))
903                        except ValueError as exc:
904                            self._raise_subnode(
905                                node, f'Failed to read ELF file: {str(exc)}')
906
907                    _gen_split_elf(node, depth, segments, entry_addr)
908
909        def _add_node(base_node, depth, node):
910            """Add nodes to the output FIT
911
912            Args:
913                base_node (Node): Base Node of the FIT (with 'description'
914                    property)
915                depth (int): Current node depth (0 is the base 'fit' node)
916                node (Node): Current node to process
917
918            There are two cases to deal with:
919                - hash and signature nodes which become part of the FIT
920                - binman entries which are used to define the 'data' for each
921                  image, so don't appear in the FIT
922            """
923            # Copy over all the relevant properties
924            for pname, prop in node.props.items():
925                _process_prop(pname, prop)
926
927            rel_path = node.path[len(base_node.path):]
928            in_images = rel_path.startswith('/images')
929
930            has_images = depth == 2 and in_images
931            if has_images:
932                image_name = rel_path[len('/images/'):]
933                entry = self._priv_entries[image_name]
934                data = entry.GetData()
935                fsw.property('data', bytes(data))
936
937            for subnode in node.subnodes:
938                if has_images and not self.IsSpecialSubnode(subnode):
939                    # This subnode is a content node not meant to appear in
940                    # the FIT (e.g. "/images/kernel/u-boot"), so don't call
941                    # fsw.add_node() or _add_node() for it.
942                    pass
943                elif self.GetImage().generate and subnode.name.startswith('@'):
944                    entry = self._priv_entries.get(subnode.name)
945                    _gen_node(subnode, depth, in_images, entry)
946                    # This is a generator (template) entry, so remove it from
947                    # the list of entries used by PackEntries(), etc. Otherwise
948                    # it will appear in the binman output
949                    to_remove.append(subnode.name)
950                else:
951                    with fsw.add_node(subnode.name):
952                        _add_node(base_node, depth + 1, subnode)
953
954        # Build a new tree with all nodes and properties starting from the
955        # entry node
956        fsw = libfdt.FdtSw()
957        fsw.INC_SIZE = 65536
958        fsw.finish_reservemap()
959        to_remove = []
960        loadables = []
961        with fsw.add_node(''):
962            _add_node(self._node, 0, self._node)
963        self._loadables = loadables
964        fdt = fsw.as_fdt()
965
966        # Remove generator entries from the main list
967        for path in to_remove:
968            if path in self._entries:
969                del self._entries[path]
970
971        # Pack this new FDT and scan it so we can add the data later
972        fdt.pack()
973        data = fdt.as_bytearray()
974        return data
975
976    def SetImagePos(self, image_pos):
977        """Set the position in the image
978
979        This sets each subentry's offsets, sizes and positions-in-image
980        according to where they ended up in the packed FIT file.
981
982        Args:
983            image_pos (int): Position of this entry in the image
984        """
985        if self.build_done:
986            return
987
988        # Skip the section processing, since we do that below. Just call the
989        # entry method
990        Entry.SetImagePos(self, image_pos)
991
992        # If mkimage is missing we'll have empty data,
993        # which will cause a FDT_ERR_BADMAGIC error
994        if self.mkimage in self.missing_bintools:
995            return
996
997        fdt = Fdt.FromData(self.GetData())
998        fdt.Scan()
999
1000        for image_name, entry in self._entries.items():
1001            path = f"/images/{image_name}"
1002            node = fdt.GetNode(path)
1003
1004            data_prop = node.props.get("data")
1005            data_pos = fdt_util.GetInt(node, "data-position")
1006            data_offset = fdt_util.GetInt(node, "data-offset")
1007            data_size = fdt_util.GetInt(node, "data-size")
1008
1009            # Contents are inside the FIT
1010            if data_prop is not None:
1011                # GetOffset() returns offset of a fdt_property struct,
1012                # which has 3 fdt32_t members before the actual data.
1013                offset = data_prop.GetOffset() + 12
1014                size = len(data_prop.bytes)
1015
1016            # External offset from the base of the FIT
1017            elif data_pos is not None:
1018                offset = data_pos
1019                size = data_size
1020
1021            # External offset from the end of the FIT, not used in binman
1022            elif data_offset is not None: # pragma: no cover
1023                offset = fdt.GetFdtObj().totalsize() + data_offset
1024                size = data_size
1025
1026            # This should never happen
1027            else: # pragma: no cover
1028                offset = None
1029                size = None
1030                self.Raise(f'{path}: missing data properties')
1031
1032            entry.SetOffsetSize(offset, size)
1033            entry.SetImagePos(image_pos + self.offset)
1034
1035    def AddBintools(self, btools):
1036        super().AddBintools(btools)
1037        self.mkimage = self.AddBintool(btools, 'mkimage')
1038        self.fdtgrep = self.AddBintool(btools, 'fdtgrep')
1039
1040    def CheckMissing(self, missing_list):
1041        # We must use our private entry list for this since generator nodes
1042        # which are removed from self._entries will otherwise not show up as
1043        # missing
1044        for entry in self._priv_entries.values():
1045            entry.CheckMissing(missing_list)
1046
1047    def CheckOptional(self, optional_list):
1048        # We must use our private entry list for this since generator nodes
1049        # which are removed from self._entries will otherwise not show up as
1050        # optional
1051        for entry in self._priv_entries.values():
1052            entry.CheckOptional(optional_list)
1053
1054    def CheckEntries(self):
1055        pass
1056
1057    def UpdateSignatures(self, privatekey_fname, algo, input_fname):
1058        uniq = self.GetUniqueName()
1059        args = [ '-G', privatekey_fname, '-r', '-o', algo, '-F' ]
1060        if input_fname:
1061            fname = input_fname
1062        else:
1063            fname = tools.get_output_filename(f'{uniq}.fit')
1064            tools.write_file(fname, self.GetData())
1065        args.append(fname)
1066
1067        if self.mkimage.run_cmd(*args) is None:
1068            self.Raise("Missing tool: 'mkimage'")
1069
1070        data = tools.read_file(fname)
1071        self.WriteData(data)
1072