1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
6 """Entry-type module for producing a FIT"""
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
16 # Supported operations, with the fit,operation property
17 OP_GEN_FDT_NODES = range(1)
19 'gen-fdt-nodes': OP_GEN_FDT_NODES,
22 class Entry_fit(Entry_section):
24 """Flat Image Tree (FIT)
26 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the
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.
32 For example, this creates an image containing a FIT with U-Boot SPL::
36 description = "Test FIT";
37 fit,fdt-list = "of-list";
56 More complex setups can be created, with generated nodes, as described
59 Properties (in the 'fit' node itself)
60 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62 Special properties have a `fit,` prefix, indicating that they should be
63 processed but not included in the final FIT.
65 The top-level 'fit' node supports the following special properties:
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.
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
80 Node names and property values support a basic string-substitution feature.
81 Available substitutions for '@' nodes (and property values) are:
84 Sequence number of the generated fdt (1, 2, ...)
86 Name of the dtb as provided (i.e. without adding '.dtb')
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`.
92 Available substitutions for property values in these nodes are:
95 Sequence number of the default fdt, as provided by the 'default-dt'
101 You can add an operation to an '@' node to indicate which operation is
105 fit,operation = "gen-fdt-nodes";
109 Available operations are:
112 Generate FDT nodes as above. This is the default if there is no
113 `fit,operation` property.
115 Generating nodes from an FDT list (gen-fdt-nodes)
116 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
124 Then add a 'generator node', a node with a name starting with '@'::
128 description = "fdt-NAME";
130 compression = "none";
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.
140 You can create config nodes in a similar way::
143 default = "@config-DEFAULT-SEQ";
145 description = "NAME";
152 This tells binman to create nodes `config-1` and `config-2`, i.e. a config
153 for each of your two files.
155 Note that if no devicetree files are provided (with '-a of-list' as above)
156 then no nodes will be generated.
158 def __init__(self, section, etype, node):
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
167 super().__init__(section, etype, node)
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)])
183 self._fdts = fdts.split()
184 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
187 def _get_operation(self, base_node, node):
188 """Get the operation referenced by a subnode
191 node (Node): Subnode (of the FIT) to check
194 int: Operation to perform
197 ValueError: Invalid operation name
199 oper_name = node.props.get('fit,operation')
201 return OP_GEN_FDT_NODES
202 oper = OPERATIONS.get(oper_name.value)
204 self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
207 def ReadEntries(self):
208 def _add_entries(base_node, depth, node):
209 """Add entries for any nodes that need them
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
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.
219 rel_path = node.path[len(base_node.path):]
220 in_images = rel_path.startswith('/images')
221 has_images = depth == 2 and in_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')
230 # The hash subnodes here are for mkimage, not binman.
231 entry.SetUpdateHash(False)
232 self._entries[rel_path] = entry
234 for subnode in node.subnodes:
235 _add_entries(base_node, depth + 1, subnode)
237 _add_entries(self._node, 0, self._node)
239 def BuildSectionData(self, required):
240 """Build FIT entry contents
242 This adds the 'data' properties to the input ITB (Image-tree Binary)
243 then runs mkimage to process it.
246 required (bool): True if the data must be present, False if it is OK
250 bytes: Contents of the section
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)
260 ext_offset = self._fit_props.get('fit,external-offset')
261 if ext_offset is not None:
264 'pad': fdt_util.fdt32_to_cpu(ext_offset.value)
266 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
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)
272 return tools.read_file(output_fname)
274 def _raise_subnode(self, node, msg):
275 """Raise an error with a paticular FIT subnode
278 node (Node): FIT subnode containing the error
279 msg (str): Message to report
282 ValueError, as requested
284 rel_path = node.path[len(self._node.path) + 1:]
285 self.Raise(f"subnode '{rel_path}': {msg}")
287 def _build_input(self):
288 """Finish the FIT by adding the 'data' properties to it
294 bytes: New fdt contents
296 def _process_prop(pname, prop):
297 """Process special properties
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.
304 pname (str): Name of property
305 prop (Prop): Property to process
307 if pname == 'default':
309 # Handle the 'default' property
310 if val.startswith('@'):
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:
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)
323 elif pname.startswith('fit,'):
324 # Ignore these, which are commands for binman to process
326 elif pname in ['offset', 'size', 'image-pos']:
327 # Don't add binman's calculated properties
329 fsw.property(pname, prop.bytes)
331 def _gen_fdt_nodes(node, depth, in_images):
332 """Generate FDT nodes
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
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
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))
356 b'SEQ', tools.to_bytes(str(seq + 1)))
357 fsw.property(pname, val)
359 # Add data for 'images' nodes (but not 'config')
360 if depth == 1 and in_images:
361 fsw.property('data', tools.read_file(fname))
363 for subnode in node.subnodes:
364 with fsw.add_node(subnode.name):
365 _add_node(node, depth + 1, subnode)
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")
372 self.Raise("Generator node requires 'fit,fdt-list' property")
374 def _gen_node(base_node, node, depth, in_images):
375 """Generate nodes from a template
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
384 base_node (Node): Base Node of the FIT (with 'description'
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
391 oper = self._get_operation(base_node, node)
392 if oper == OP_GEN_FDT_NODES:
393 _gen_fdt_nodes(node, depth, in_images)
395 def _add_node(base_node, depth, node):
396 """Add nodes to the output FIT
399 base_node (Node): Base Node of the FIT (with 'description'
401 depth (int): Current node depth (0 is the base 'fit' node)
402 node (Node): Current node to process
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
409 # Copy over all the relevant properties
410 for pname, prop in node.props.items():
411 _process_prop(pname, prop)
413 rel_path = node.path[len(base_node.path):]
414 in_images = rel_path.startswith('/images')
416 has_images = depth == 2 and in_images
418 entry = self._entries[rel_path]
419 data = entry.GetData()
420 fsw.property('data', bytes(data))
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.
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)
434 del self._entries[subnode_path]
436 with fsw.add_node(subnode.name):
437 _add_node(base_node, depth + 1, subnode)
439 # Build a new tree with all nodes and properties starting from the
442 fsw.finish_reservemap()
443 with fsw.add_node(''):
444 _add_node(self._node, 0, self._node)
447 # Pack this new FDT and scan it so we can add the data later
449 data = fdt.as_bytearray()
452 def SetImagePos(self, image_pos):
453 """Set the position in the image
455 This sets each subentry's offsets, sizes and positions-in-image
456 according to where they ended up in the packed FIT file.
459 image_pos (int): Position of this entry in the image
461 super().SetImagePos(image_pos)
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:
468 fdt = Fdt.FromData(self.GetData())
471 for path, section in self._entries.items():
472 node = fdt.GetNode(path)
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")
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)
486 # External offset from the base of the FIT
487 elif data_pos is not None:
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
496 # This should never happen
497 else: # pragma: no cover
498 self.Raise(f'{path}: missing data properties')
500 section.SetOffsetSize(offset, size)
501 section.SetImagePos(self.image_pos)
503 def AddBintools(self, btools):
504 super().AddBintools(btools)
505 self.mkimage = self.AddBintool(btools, 'mkimage')