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