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