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