1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Entry-type module for producing a FIT
8 from collections import defaultdict, OrderedDict
11 from binman.entry import Entry, EntryArg
12 from binman.etype.section import Entry_section
13 from dtoc import fdt_util
14 from dtoc.fdt import Fdt
15 from patman import tools
17 # Supported operations, with the fit,operation property
18 OP_GEN_FDT_NODES = range(1)
20 'gen-fdt-nodes': OP_GEN_FDT_NODES,
23 class Entry_fit(Entry_section):
25 """Flat Image Tree (FIT)
27 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
30 Nodes for the FIT should be written out in the binman configuration just as
31 they would be in a file passed to mkimage.
33 For example, this creates an image containing a FIT with U-Boot SPL::
37 description = "Test FIT";
38 fit,fdt-list = "of-list";
57 More complex setups can be created, with generated nodes, as described
60 Properties (in the 'fit' node itself)
61 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 Special properties have a `fit,` prefix, indicating that they should be
64 processed but not included in the final FIT.
66 The top-level 'fit' node supports the following special properties:
69 Indicates that the contents of the FIT are external and provides the
70 external offset. This is passed to mkimage via the -E and -p flags.
73 Indicates the entry argument which provides the list of device tree
74 files for the gen-fdt-nodes operation (as below). This is often
75 `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed
81 Node names and property values support a basic string-substitution feature.
82 Available substitutions for '@' nodes (and property values) are:
85 Sequence number of the generated fdt (1, 2, ...)
87 Name of the dtb as provided (i.e. without adding '.dtb')
89 The `default` property, if present, will be automatically set to the name
90 if of configuration whose devicetree matches the `default-dt` entry
91 argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`.
93 Available substitutions for property values in these nodes are:
96 Sequence number of the default fdt, as provided by the 'default-dt'
102 You can add an operation to an '@' node to indicate which operation is
106 fit,operation = "gen-fdt-nodes";
110 Available operations are:
113 Generate FDT nodes as above. This is the default if there is no
114 `fit,operation` property.
116 Generating nodes from an FDT list (gen-fdt-nodes)
117 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
119 U-Boot supports creating fdt and config nodes automatically. To do this,
120 pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells
121 binman that you want to generates nodes for two files: `file1.dtb` and
122 `file2.dtb`. The `fit,fdt-list` property (see above) indicates that
123 `of-list` should be used. If the property is missing you will get an error.
125 Then add a 'generator node', a node with a name starting with '@'::
129 description = "fdt-NAME";
131 compression = "none";
135 This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
136 files. All the properties you specify will be included in the node. This
137 node acts like a template to generate the nodes. The generator node itself
138 does not appear in the output - it is replaced with what binman generates.
139 A 'data' property is created with the contents of the FDT file.
141 You can create config nodes in a similar way::
144 default = "@config-DEFAULT-SEQ";
146 description = "NAME";
153 This tells binman to create nodes `config-1` and `config-2`, i.e. a config
154 for each of your two files.
156 Note that if no devicetree files are provided (with '-a of-list' as above)
157 then no nodes will be generated.
159 def __init__(self, section, etype, node):
162 _fit: FIT file being built
163 _entries: dict from Entry_section:
164 key: relative path to entry Node (from the base of the FIT)
165 value: Entry_section object comprising the contents of this
168 super().__init__(section, etype, node)
176 for pname, prop in self._node.props.items():
177 if pname.startswith('fit,'):
178 self._fit_props[pname] = prop
179 self._fit_list_prop = self._fit_props.get('fit,fdt-list')
180 if self._fit_list_prop:
181 fdts, = self.GetEntryArgsOrProps(
182 [EntryArg(self._fit_list_prop.value, str)])
184 self._fdts = fdts.split()
185 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
188 def _get_operation(self, subnode):
189 """Get the operation referenced by a subnode
192 subnode (Node): Subnode (of the FIT) to check
195 int: Operation to perform
198 ValueError: Invalid operation name
200 oper_name = subnode.props.get('fit,operation')
202 return OP_GEN_FDT_NODES
203 oper = OPERATIONS.get(oper_name.value)
205 self.Raise(f"Unknown operation '{oper_name.value}'")
208 def ReadEntries(self):
209 def _add_entries(base_node, depth, node):
210 """Add entries for any nodes that need them
213 base_node: Base Node of the FIT (with 'description' property)
214 depth: Current node depth (0 is the base 'fit' node)
215 node: Current node to process
217 Here we only need to provide binman entries which are used to define
218 the 'data' for each image. We create an entry_Section for each.
220 rel_path = node.path[len(base_node.path):]
221 in_images = rel_path.startswith('/images')
222 has_images = depth == 2 and in_images
224 # This node is a FIT subimage node (e.g. "/images/kernel")
225 # containing content nodes. We collect the subimage nodes and
226 # section entries for them here to merge the content subnodes
227 # together and put the merged contents in the subimage node's
228 # 'data' property later.
229 entry = Entry.Create(self.section, node, etype='section')
231 # The hash subnodes here are for mkimage, not binman.
232 entry.SetUpdateHash(False)
233 self._entries[rel_path] = entry
235 for subnode in node.subnodes:
236 _add_entries(base_node, depth + 1, subnode)
238 _add_entries(self._node, 0, self._node)
240 def BuildSectionData(self, required):
241 """Build FIT entry contents
243 This adds the 'data' properties to the input ITB (Image-tree Binary)
244 then runs mkimage to process it.
247 required: True if the data must be present, False if it is OK to
251 Contents of the section (bytes)
253 data = self._BuildInput()
254 uniq = self.GetUniqueName()
255 input_fname = tools.get_output_filename('%s.itb' % uniq)
256 output_fname = tools.get_output_filename('%s.fit' % uniq)
257 tools.write_file(input_fname, data)
258 tools.write_file(output_fname, data)
261 ext_offset = self._fit_props.get('fit,external-offset')
262 if ext_offset is not None:
265 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
267 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
269 # Bintool is missing; just use empty data as the output
270 self.record_missing_bintool(self.mkimage)
271 return tools.get_bytes(0, 1024)
273 return tools.read_file(output_fname)
275 def _BuildInput(self):
276 """Finish the FIT by adding the 'data' properties to it
282 New fdt contents (bytes)
284 def _process_prop(pname, prop):
285 """Process special properties
287 Handles properties with generated values. At present the only
288 supported property is 'default', i.e. the default device tree in
289 the configurations node.
292 pname (str): Name of property
293 prop (Prop): Property to process
295 if pname == 'default':
297 # Handle the 'default' property
298 if val.startswith('@'):
301 if not self._fit_default_dt:
302 self.Raise("Generated 'default' node requires default-dt entry argument")
303 if self._fit_default_dt not in self._fdts:
304 self.Raise("default-dt entry argument '%s' not found in fdt list: %s" %
305 (self._fit_default_dt,
306 ', '.join(self._fdts)))
307 seq = self._fdts.index(self._fit_default_dt)
308 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
309 fsw.property_string(pname, val)
311 elif pname.startswith('fit,'):
312 # Ignore these, which are commands for binman to process
314 elif pname in ['offset', 'size', 'image-pos']:
315 # Don't add binman's calculated properties
317 fsw.property(pname, prop.bytes)
319 def _gen_fdt_nodes(subnode, depth, in_images):
320 """Generate FDT nodes
322 This creates one node for each member of self._fdts using the
323 provided template. If a property value contains 'NAME' it is
324 replaced with the filename of the FDT. If a property value contains
325 SEQ it is replaced with the node sequence number, where 1 is the
329 subnode (None): Generator node to process
330 depth: Current node depth (0 is the base 'fit' node)
331 in_images: True if this is inside the 'images' node, so that
332 'data' properties should be generated
335 # Generate nodes for each FDT
336 for seq, fdt_fname in enumerate(self._fdts):
337 node_name = subnode.name[1:].replace('SEQ', str(seq + 1))
338 fname = tools.get_input_filename(fdt_fname + '.dtb')
339 with fsw.add_node(node_name):
340 for pname, prop in subnode.props.items():
341 val = prop.bytes.replace(
342 b'NAME', tools.to_bytes(fdt_fname))
344 b'SEQ', tools.to_bytes(str(seq + 1)))
345 fsw.property(pname, val)
347 # Add data for 'images' nodes (but not 'config')
348 if depth == 1 and in_images:
349 fsw.property('data', tools.read_file(fname))
351 for subnode in node.subnodes:
352 with fsw.add_node(subnode.name):
353 _add_node(node, depth + 1, subnode)
355 if self._fdts is None:
356 if self._fit_list_prop:
357 self.Raise("Generator node requires '%s' entry argument" %
358 self._fit_list_prop.value)
360 self.Raise("Generator node requires 'fit,fdt-list' property")
362 def _gen_node(subnode, depth, in_images):
363 """Generate nodes from a template
365 This creates one node for each member of self._fdts using the
366 provided template. If a property value contains 'NAME' it is
367 replaced with the filename of the FDT. If a property value contains
368 SEQ it is replaced with the node sequence number, where 1 is the
372 subnode (None): Generator node to process
373 depth: Current node depth (0 is the base 'fit' node)
374 in_images: True if this is inside the 'images' node, so that
375 'data' properties should be generated
377 oper = self._get_operation(subnode)
378 if oper == OP_GEN_FDT_NODES:
379 _gen_fdt_nodes(subnode, depth, in_images)
381 def _add_node(base_node, depth, node):
382 """Add nodes to the output FIT
385 base_node: Base Node of the FIT (with 'description' property)
386 depth: Current node depth (0 is the base 'fit' node)
387 node: Current node to process
389 There are two cases to deal with:
390 - hash and signature nodes which become part of the FIT
391 - binman entries which are used to define the 'data' for each
392 image, so don't appear in the FIT
394 # Copy over all the relevant properties
395 for pname, prop in node.props.items():
396 _process_prop(pname, prop)
398 rel_path = node.path[len(base_node.path):]
399 in_images = rel_path.startswith('/images')
401 has_images = depth == 2 and in_images
403 entry = self._entries[rel_path]
404 data = entry.GetData()
405 fsw.property('data', bytes(data))
407 for subnode in node.subnodes:
408 if has_images and not (subnode.name.startswith('hash') or
409 subnode.name.startswith('signature')):
410 # This subnode is a content node not meant to appear in
411 # the FIT (e.g. "/images/kernel/u-boot"), so don't call
412 # fsw.add_node() or _add_node() for it.
414 elif self.GetImage().generate and subnode.name.startswith('@'):
415 subnode_path = f'{rel_path}/{subnode.name}'
416 entry = self._entries.get(subnode_path)
417 _gen_node(subnode, depth, in_images)
419 del self._entries[subnode_path]
421 with fsw.add_node(subnode.name):
422 _add_node(base_node, depth + 1, subnode)
424 # Build a new tree with all nodes and properties starting from the
427 fsw.finish_reservemap()
428 with fsw.add_node(''):
429 _add_node(self._node, 0, self._node)
432 # Pack this new FDT and scan it so we can add the data later
434 data = fdt.as_bytearray()
437 def SetImagePos(self, image_pos):
438 """Set the position in the image
440 This sets each subentry's offsets, sizes and positions-in-image
441 according to where they ended up in the packed FIT file.
444 image_pos: Position of this entry in the image
446 super().SetImagePos(image_pos)
448 # If mkimage is missing we'll have empty data,
449 # which will cause a FDT_ERR_BADMAGIC error
450 if self.mkimage in self.missing_bintools:
453 fdt = Fdt.FromData(self.GetData())
456 for path, section in self._entries.items():
457 node = fdt.GetNode(path)
459 data_prop = node.props.get("data")
460 data_pos = fdt_util.GetInt(node, "data-position")
461 data_offset = fdt_util.GetInt(node, "data-offset")
462 data_size = fdt_util.GetInt(node, "data-size")
464 # Contents are inside the FIT
465 if data_prop is not None:
466 # GetOffset() returns offset of a fdt_property struct,
467 # which has 3 fdt32_t members before the actual data.
468 offset = data_prop.GetOffset() + 12
469 size = len(data_prop.bytes)
471 # External offset from the base of the FIT
472 elif data_pos is not None:
476 # External offset from the end of the FIT, not used in binman
477 elif data_offset is not None: # pragma: no cover
478 offset = fdt.GetFdtObj().totalsize() + data_offset
481 # This should never happen
482 else: # pragma: no cover
483 self.Raise("%s: missing data properties" % (path))
485 section.SetOffsetSize(offset, size)
486 section.SetImagePos(self.image_pos)
488 def AddBintools(self, btools):
489 super().AddBintools(btools)
490 self.mkimage = self.AddBintool(btools, 'mkimage')