cec7413540dfbc46703688b6ba45e361c30503d9
[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 dtoc import fdt_util
13 from dtoc.fdt import Fdt
14 from patman import tools
15
16 # Supported operations, with the fit,operation property
17 OP_GEN_FDT_NODES = range(1)
18 OPERATIONS = {
19     'gen-fdt-nodes': OP_GEN_FDT_NODES,
20     }
21
22 class Entry_fit(Entry_section):
23
24     """Flat Image Tree (FIT)
25
26     This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
27     input provided.
28
29     Nodes for the FIT should be written out in the binman configuration just as
30     they would be in a file passed to mkimage.
31
32     For example, this creates an image containing a FIT with U-Boot SPL::
33
34         binman {
35             fit {
36                 description = "Test FIT";
37                 fit,fdt-list = "of-list";
38
39                 images {
40                     kernel@1 {
41                         description = "SPL";
42                         os = "u-boot";
43                         type = "rkspi";
44                         arch = "arm";
45                         compression = "none";
46                         load = <0>;
47                         entry = <0>;
48
49                         u-boot-spl {
50                         };
51                     };
52                 };
53             };
54         };
55
56     More complex setups can be created, with generated nodes, as described
57     below.
58
59     Properties (in the 'fit' node itself)
60     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61
62     Special properties have a `fit,` prefix, indicating that they should be
63     processed but not included in the final FIT.
64
65     The top-level 'fit' node supports the following special properties:
66
67         fit,external-offset
68             Indicates that the contents of the FIT are external and provides the
69             external offset. This is passed to mkimage via the -E and -p flags.
70
71         fit,fdt-list
72             Indicates the entry argument which provides the list of device tree
73             files for the gen-fdt-nodes operation (as below). This is often
74             `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
75             to binman.
76
77     Substitutions
78     ~~~~~~~~~~~~~
79
80     Node names and property values support a basic string-substitution feature.
81     Available substitutions for '@' nodes (and property values) are:
82
83     SEQ:
84         Sequence number of the generated fdt (1, 2, ...)
85     NAME
86         Name of the dtb as provided (i.e. without adding '.dtb')
87
88     The `default` property, if present, will be automatically set to the name
89     if of configuration whose devicetree matches the `default-dt` entry
90     argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
91
92     Available substitutions for property values in these nodes are:
93
94     DEFAULT-SEQ:
95         Sequence number of the default fdt, as provided by the 'default-dt'
96         entry argument
97
98     Available operations
99     ~~~~~~~~~~~~~~~~~~~~
100
101     You can add an operation to an '@' node to indicate which operation is
102     required::
103
104         @fdt-SEQ {
105             fit,operation = "gen-fdt-nodes";
106             ...
107         };
108
109     Available operations are:
110
111     gen-fdt-nodes
112         Generate FDT nodes as above. This is the default if there is no
113         `fit,operation` property.
114
115     Generating nodes from an FDT list (gen-fdt-nodes)
116     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117
118     U-Boot supports creating fdt and config nodes automatically. To do this,
119     pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
120     binman that you want to generates nodes for two files: `file1.dtb` and
121     `file2.dtb`. The `fit,fdt-list` property (see above) indicates that
122     `of-list` should be used. If the property is missing you will get an error.
123
124     Then add a 'generator node', a node with a name starting with '@'::
125
126         images {
127             @fdt-SEQ {
128                 description = "fdt-NAME";
129                 type = "flat_dt";
130                 compression = "none";
131             };
132         };
133
134     This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
135     files. All the properties you specify will be included in the node. This
136     node acts like a template to generate the nodes. The generator node itself
137     does not appear in the output - it is replaced with what binman generates.
138     A 'data' property is created with the contents of the FDT file.
139
140     You can create config nodes in a similar way::
141
142         configurations {
143             default = "@config-DEFAULT-SEQ";
144             @config-SEQ {
145                 description = "NAME";
146                 firmware = "atf";
147                 loadables = "uboot";
148                 fdt = "fdt-SEQ";
149             };
150         };
151
152     This tells binman to create nodes `config-1` and `config-2`, i.e. a config
153     for each of your two files.
154
155     Note that if no devicetree files are provided (with '-a of-list' as above)
156     then no nodes will be generated.
157     """
158     def __init__(self, section, etype, node):
159         """
160         Members:
161             _fit: FIT file being built
162             _entries: dict from Entry_section:
163                 key: relative path to entry Node (from the base of the FIT)
164                 value: Entry_section object comprising the contents of this
165                     node
166         """
167         super().__init__(section, etype, node)
168         self._fit = None
169         self._fit_props = {}
170         self._fdts = None
171         self.mkimage = None
172
173     def ReadNode(self):
174         super().ReadNode()
175         for pname, prop in self._node.props.items():
176             if pname.startswith('fit,'):
177                 self._fit_props[pname] = prop
178         self._fit_list_prop = self._fit_props.get('fit,fdt-list')
179         if self._fit_list_prop:
180             fdts, = self.GetEntryArgsOrProps(
181                 [EntryArg(self._fit_list_prop.value, str)])
182             if fdts is not None:
183                 self._fdts = fdts.split()
184         self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
185                                                                   str)])[0]
186
187     def _get_operation(self, base_node, node):
188         """Get the operation referenced by a subnode
189
190         Args:
191             node (Node): Subnode (of the FIT) to check
192
193         Returns:
194             int: Operation to perform
195
196         Raises:
197             ValueError: Invalid operation name
198         """
199         oper_name = node.props.get('fit,operation')
200         if not oper_name:
201             return OP_GEN_FDT_NODES
202         oper = OPERATIONS.get(oper_name.value)
203         if oper is None:
204             self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
205         return oper
206
207     def ReadEntries(self):
208         def _add_entries(base_node, depth, node):
209             """Add entries for any nodes that need them
210
211             Args:
212                 base_node: Base Node of the FIT (with 'description' property)
213                 depth: Current node depth (0 is the base 'fit' node)
214                 node: Current node to process
215
216             Here we only need to provide binman entries which are used to define
217             the 'data' for each image. We create an entry_Section for each.
218             """
219             rel_path = node.path[len(base_node.path):]
220             in_images = rel_path.startswith('/images')
221             has_images = depth == 2 and in_images
222             if has_images:
223                 # This node is a FIT subimage node (e.g. "/images/kernel")
224                 # containing content nodes. We collect the subimage nodes and
225                 # section entries for them here to merge the content subnodes
226                 # together and put the merged contents in the subimage node's
227                 # 'data' property later.
228                 entry = Entry.Create(self.section, node, etype='section')
229                 entry.ReadNode()
230                 # The hash subnodes here are for mkimage, not binman.
231                 entry.SetUpdateHash(False)
232                 self._entries[rel_path] = entry
233
234             for subnode in node.subnodes:
235                 _add_entries(base_node, depth + 1, subnode)
236
237         _add_entries(self._node, 0, self._node)
238
239     def BuildSectionData(self, required):
240         """Build FIT entry contents
241
242         This adds the 'data' properties to the input ITB (Image-tree Binary)
243         then runs mkimage to process it.
244
245         Args:
246             required (bool): True if the data must be present, False if it is OK
247                 to return None
248
249         Returns:
250             bytes: Contents of the section
251         """
252         data = self._build_input()
253         uniq = self.GetUniqueName()
254         input_fname = tools.get_output_filename(f'{uniq}.itb')
255         output_fname = tools.get_output_filename(f'{uniq}.fit')
256         tools.write_file(input_fname, data)
257         tools.write_file(output_fname, data)
258
259         args = {}
260         ext_offset = self._fit_props.get('fit,external-offset')
261         if ext_offset is not None:
262             args = {
263                 'external': True,
264                 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
265                 }
266         if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
267                             **args) is None:
268             # Bintool is missing; just use empty data as the output
269             self.record_missing_bintool(self.mkimage)
270             return tools.get_bytes(0, 1024)
271
272         return tools.read_file(output_fname)
273
274     def _raise_subnode(self, node, msg):
275         """Raise an error with a paticular FIT subnode
276
277         Args:
278             node (Node): FIT subnode containing the error
279             msg (str): Message to report
280
281         Raises:
282             ValueError, as requested
283         """
284         rel_path = node.path[len(self._node.path) + 1:]
285         self.Raise(f"subnode '{rel_path}': {msg}")
286
287     def _build_input(self):
288         """Finish the FIT by adding the 'data' properties to it
289
290         Arguments:
291             fdt: FIT to update
292
293         Returns:
294             bytes: New fdt contents
295         """
296         def _process_prop(pname, prop):
297             """Process special properties
298
299             Handles properties with generated values. At present the only
300             supported property is 'default', i.e. the default device tree in
301             the configurations node.
302
303             Args:
304                 pname (str): Name of property
305                 prop (Prop): Property to process
306             """
307             if pname == 'default':
308                 val = prop.value
309                 # Handle the 'default' property
310                 if val.startswith('@'):
311                     if not self._fdts:
312                         return
313                     if not self._fit_default_dt:
314                         self.Raise("Generated 'default' node requires default-dt entry argument")
315                     if self._fit_default_dt not in self._fdts:
316                         self.Raise(
317                             f"default-dt entry argument '{self._fit_default_dt}' "
318                             f"not found in fdt list: {', '.join(self._fdts)}")
319                     seq = self._fdts.index(self._fit_default_dt)
320                     val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
321                     fsw.property_string(pname, val)
322                     return
323             elif pname.startswith('fit,'):
324                 # Ignore these, which are commands for binman to process
325                 return
326             elif pname in ['offset', 'size', 'image-pos']:
327                 # Don't add binman's calculated properties
328                 return
329             fsw.property(pname, prop.bytes)
330
331         def _gen_fdt_nodes(node, depth, in_images):
332             """Generate FDT nodes
333
334             This creates one node for each member of self._fdts using the
335             provided template. If a property value contains 'NAME' it is
336             replaced with the filename of the FDT. If a property value contains
337             SEQ it is replaced with the node sequence number, where 1 is the
338             first.
339
340             Args:
341                 node (None): Generator node to process
342                 depth: Current node depth (0 is the base 'fit' node)
343                 in_images: True if this is inside the 'images' node, so that
344                     'data' properties should be generated
345             """
346             if self._fdts:
347                 # Generate nodes for each FDT
348                 for seq, fdt_fname in enumerate(self._fdts):
349                     node_name = node.name[1:].replace('SEQ', str(seq + 1))
350                     fname = tools.get_input_filename(fdt_fname + '.dtb')
351                     with fsw.add_node(node_name):
352                         for pname, prop in node.props.items():
353                             val = prop.bytes.replace(
354                                 b'NAME', tools.to_bytes(fdt_fname))
355                             val = val.replace(
356                                 b'SEQ', tools.to_bytes(str(seq + 1)))
357                             fsw.property(pname, val)
358
359                         # Add data for 'images' nodes (but not 'config')
360                         if depth == 1 and in_images:
361                             fsw.property('data', tools.read_file(fname))
362
363                         for subnode in node.subnodes:
364                             with fsw.add_node(subnode.name):
365                                 _add_node(node, depth + 1, subnode)
366             else:
367                 if self._fdts is None:
368                     if self._fit_list_prop:
369                         self.Raise('Generator node requires '
370                             f"'{self._fit_list_prop.value}' entry argument")
371                     else:
372                         self.Raise("Generator node requires 'fit,fdt-list' property")
373
374         def _gen_node(base_node, node, depth, in_images):
375             """Generate nodes from a template
376
377             This creates one node for each member of self._fdts using the
378             provided template. If a property value contains 'NAME' it is
379             replaced with the filename of the FDT. If a property value contains
380             SEQ it is replaced with the node sequence number, where 1 is the
381             first.
382
383             Args:
384                 base_node (Node): Base Node of the FIT (with 'description'
385                     property)
386                 node (Node): Generator node to process
387                 depth (int): Current node depth (0 is the base 'fit' node)
388                 in_images (bool): True if this is inside the 'images' node, so
389                     that 'data' properties should be generated
390             """
391             oper = self._get_operation(base_node, node)
392             if oper == OP_GEN_FDT_NODES:
393                 _gen_fdt_nodes(node, depth, in_images)
394
395         def _add_node(base_node, depth, node):
396             """Add nodes to the output FIT
397
398             Args:
399                 base_node (Node): Base Node of the FIT (with 'description'
400                     property)
401                 depth (int): Current node depth (0 is the base 'fit' node)
402                 node (Node): Current node to process
403
404             There are two cases to deal with:
405                 - hash and signature nodes which become part of the FIT
406                 - binman entries which are used to define the 'data' for each
407                   image, so don't appear in the FIT
408             """
409             # Copy over all the relevant properties
410             for pname, prop in node.props.items():
411                 _process_prop(pname, prop)
412
413             rel_path = node.path[len(base_node.path):]
414             in_images = rel_path.startswith('/images')
415
416             has_images = depth == 2 and in_images
417             if has_images:
418                 entry = self._entries[rel_path]
419                 data = entry.GetData()
420                 fsw.property('data', bytes(data))
421
422             for subnode in node.subnodes:
423                 if has_images and not (subnode.name.startswith('hash') or
424                                        subnode.name.startswith('signature')):
425                     # This subnode is a content node not meant to appear in
426                     # the FIT (e.g. "/images/kernel/u-boot"), so don't call
427                     # fsw.add_node() or _add_node() for it.
428                     pass
429                 elif self.GetImage().generate and subnode.name.startswith('@'):
430                     subnode_path = f'{rel_path}/{subnode.name}'
431                     entry = self._entries.get(subnode_path)
432                     _gen_node(base_node, subnode, depth, in_images)
433                     if entry:
434                         del self._entries[subnode_path]
435                 else:
436                     with fsw.add_node(subnode.name):
437                         _add_node(base_node, depth + 1, subnode)
438
439         # Build a new tree with all nodes and properties starting from the
440         # entry node
441         fsw = libfdt.FdtSw()
442         fsw.finish_reservemap()
443         with fsw.add_node(''):
444             _add_node(self._node, 0, self._node)
445         fdt = fsw.as_fdt()
446
447         # Pack this new FDT and scan it so we can add the data later
448         fdt.pack()
449         data = fdt.as_bytearray()
450         return data
451
452     def SetImagePos(self, image_pos):
453         """Set the position in the image
454
455         This sets each subentry's offsets, sizes and positions-in-image
456         according to where they ended up in the packed FIT file.
457
458         Args:
459             image_pos (int): Position of this entry in the image
460         """
461         super().SetImagePos(image_pos)
462
463         # If mkimage is missing we'll have empty data,
464         # which will cause a FDT_ERR_BADMAGIC error
465         if self.mkimage in self.missing_bintools:
466             return
467
468         fdt = Fdt.FromData(self.GetData())
469         fdt.Scan()
470
471         for path, section in self._entries.items():
472             node = fdt.GetNode(path)
473
474             data_prop = node.props.get("data")
475             data_pos = fdt_util.GetInt(node, "data-position")
476             data_offset = fdt_util.GetInt(node, "data-offset")
477             data_size = fdt_util.GetInt(node, "data-size")
478
479             # Contents are inside the FIT
480             if data_prop is not None:
481                 # GetOffset() returns offset of a fdt_property struct,
482                 # which has 3 fdt32_t members before the actual data.
483                 offset = data_prop.GetOffset() + 12
484                 size = len(data_prop.bytes)
485
486             # External offset from the base of the FIT
487             elif data_pos is not None:
488                 offset = data_pos
489                 size = data_size
490
491             # External offset from the end of the FIT, not used in binman
492             elif data_offset is not None: # pragma: no cover
493                 offset = fdt.GetFdtObj().totalsize() + data_offset
494                 size = data_size
495
496             # This should never happen
497             else: # pragma: no cover
498                 self.Raise(f'{path}: missing data properties')
499
500             section.SetOffsetSize(offset, size)
501             section.SetImagePos(self.image_pos)
502
503     def AddBintools(self, btools):
504         super().AddBintools(btools)
505         self.mkimage = self.AddBintool(btools, 'mkimage')