]> git.ipfire.org Git - thirdparty/u-boot.git/blame - tools/binman/etype/fit.py
Merge branch 'next'
[thirdparty/u-boot.git] / tools / binman / etype / fit.py
CommitLineData
fdc34368
SG
1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
fdc34368 5
5795497e
SG
6"""Entry-type module for producing a FIT"""
7
fdc34368
SG
8import libfdt
9
6cf9953b 10from binman.entry import Entry, EntryArg
f3078d4e 11from binman.etype.section import Entry_section
40c8bdd8 12from binman import elf
fdc34368
SG
13from dtoc import fdt_util
14from dtoc.fdt import Fdt
4583c002 15from u_boot_pylib import tools
fdc34368 16
6a0b5f8b 17# Supported operations, with the fit,operation property
40c8bdd8 18OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2)
6a0b5f8b
SG
19OPERATIONS = {
20 'gen-fdt-nodes': OP_GEN_FDT_NODES,
40c8bdd8 21 'split-elf': OP_SPLIT_ELF,
6a0b5f8b
SG
22 }
23
f3078d4e 24class Entry_fit(Entry_section):
6a0b5f8b 25
96d340e9 26 """Flat Image Tree (FIT)
fdc34368
SG
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
6bc4309b 34 For example, this creates an image containing a FIT with U-Boot SPL::
fdc34368
SG
35
36 binman {
37 fit {
38 description = "Test FIT";
6cf9953b 39 fit,fdt-list = "of-list";
fdc34368
SG
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
6a0b5f8b
SG
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
9b2fd2d2
JK
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
6a0b5f8b
SG
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
40c8bdd8
SG
122 split-elf
123 Split an ELF file into a separate node for each segment.
124
6a0b5f8b
SG
125 Generating nodes from an FDT list (gen-fdt-nodes)
126 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127
6cf9953b 128 U-Boot supports creating fdt and config nodes automatically. To do this,
98e0de3f
SG
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.
6cf9953b 133
6bc4309b 134 Then add a 'generator node', a node with a name starting with '@'::
6cf9953b
SG
135
136 images {
137 @fdt-SEQ {
138 description = "fdt-NAME";
139 type = "flat_dt";
140 compression = "none";
141 };
142 };
143
98e0de3f 144 This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two
6cf9953b
SG
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.
98e0de3f 148 A 'data' property is created with the contents of the FDT file.
6cf9953b 149
6bc4309b 150 You can create config nodes in a similar way::
6cf9953b
SG
151
152 configurations {
153 default = "@config-DEFAULT-SEQ";
154 @config-SEQ {
155 description = "NAME";
68158d59
SH
156 firmware = "atf";
157 loadables = "uboot";
6cf9953b
SG
158 fdt = "fdt-SEQ";
159 };
160 };
161
98e0de3f
SG
162 This tells binman to create nodes `config-1` and `config-2`, i.e. a config
163 for each of your two files.
6cf9953b 164
6cf9953b
SG
165 Note that if no devicetree files are provided (with '-a of-list' as above)
166 then no nodes will be generated.
40c8bdd8
SG
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
f584d44c
JK
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
40c8bdd8
SG
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";
98463903 215 load = <CONFIG_TEXT_BASE>;
40c8bdd8
SG
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 };
00b3d53f
JK
237 hash {
238 algo = "sha256";
239 };
40c8bdd8
SG
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 };
00b3d53f
JK
255 hash {
256 algo = "sha256";
257 };
40c8bdd8
SG
258 };
259 };
260
261 configurations {
262 default = "@config-DEFAULT-SEQ";
263 @config-SEQ {
264 description = "conf-NAME.dtb";
265 fdt = "fdt-SEQ";
f584d44c 266 fit,firmware = "atf-1", "u-boot";
40c8bdd8
SG
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";
00b3d53f
JK
286 hash {
287 algo = "sha256";
288 value = <...hash of first segment...>;
289 };
40c8bdd8
SG
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";
00b3d53f
JK
299 hash {
300 algo = "sha256";
301 value = <...hash of second segment...>;
302 };
40c8bdd8
SG
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 {
f584d44c 321 loadables = "u-boot", "atf-2", "atf-3", "tee-1", "tee-2";
40c8bdd8
SG
322 description = "rk3399-firefly.dtb";
323 fdt = "fdt-1";
f584d44c 324 firmware = "atf-1";
40c8bdd8
SG
325 };
326 };
327
f584d44c
JK
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.
fdc34368
SG
330 """
331 def __init__(self, section, etype, node):
332 """
333 Members:
334 _fit: FIT file being built
f3078d4e 335 _entries: dict from Entry_section:
fdc34368 336 key: relative path to entry Node (from the base of the FIT)
fe05701b 337 value: Entry_section object comprising the contents of this
fdc34368 338 node
2337eca2
SG
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
40c8bdd8 343 _loadables: List of generated split-elf nodes, each a node name
fdc34368
SG
344 """
345 super().__init__(section, etype, node)
346 self._fit = None
fdc34368 347 self._fit_props = {}
d32169c0
SG
348 self._fdts = None
349 self.mkimage = None
2337eca2 350 self._priv_entries = {}
40c8bdd8 351 self._loadables = []
f3078d4e 352
d32169c0
SG
353 def ReadNode(self):
354 super().ReadNode()
6cf9953b
SG
355 for pname, prop in self._node.props.items():
356 if pname.startswith('fit,'):
357 self._fit_props[pname] = prop
6cf9953b
SG
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()
c0f1ebe9
SG
364 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt',
365 str)])[0]
fdc34368 366
ce4e402a 367 def _get_operation(self, base_node, node):
6a0b5f8b
SG
368 """Get the operation referenced by a subnode
369
370 Args:
ce4e402a 371 node (Node): Subnode (of the FIT) to check
6a0b5f8b
SG
372
373 Returns:
374 int: Operation to perform
375
376 Raises:
377 ValueError: Invalid operation name
378 """
ce4e402a 379 oper_name = node.props.get('fit,operation')
6a0b5f8b
SG
380 if not oper_name:
381 return OP_GEN_FDT_NODES
382 oper = OPERATIONS.get(oper_name.value)
ce4e402a
SG
383 if oper is None:
384 self._raise_subnode(node, f"Unknown operation '{oper_name.value}'")
6a0b5f8b
SG
385 return oper
386
5bf81216 387 def ReadEntries(self):
38397d08
SG
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.
74d3b231 408 entry = Entry.Create(self, node, etype='section')
38397d08
SG
409 entry.ReadNode()
410 # The hash subnodes here are for mkimage, not binman.
411 entry.SetUpdateHash(False)
e736878b
ANY
412 image_name = rel_path[len('/images/'):]
413 self._entries[image_name] = entry
38397d08
SG
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
67a05017
SG
420 # Keep a copy of all entries, including generator entries, since those
421 # are removed from self._entries later.
2337eca2
SG
422 self._priv_entries = dict(self._entries)
423
38397d08
SG
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:
5795497e
SG
431 required (bool): True if the data must be present, False if it is OK
432 to return None
38397d08
SG
433
434 Returns:
5795497e 435 bytes: Contents of the section
38397d08 436 """
5795497e 437 data = self._build_input()
38397d08 438 uniq = self.GetUniqueName()
5795497e
SG
439 input_fname = tools.get_output_filename(f'{uniq}.itb')
440 output_fname = tools.get_output_filename(f'{uniq}.fit')
38397d08
SG
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 }
9b2fd2d2
JK
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)})
38397d08
SG
454 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
455 **args) is None:
033828cf
SG
456 if not self.GetAllowMissing():
457 self.Raise("Missing tool: 'mkimage'")
38397d08
SG
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
ce4e402a
SG
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
5795497e 477 def _build_input(self):
38397d08
SG
478 """Finish the FIT by adding the 'data' properties to it
479
480 Arguments:
481 fdt: FIT to update
482
483 Returns:
5795497e 484 bytes: New fdt contents
38397d08 485 """
dbe17c00
SG
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:
5795497e
SG
506 self.Raise(
507 f"default-dt entry argument '{self._fit_default_dt}' "
508 f"not found in fdt list: {', '.join(self._fdts)}")
dbe17c00
SG
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
38397d08
SG
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
dbe17c00
SG
519 fsw.property(pname, prop.bytes)
520
f584d44c
JK
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
40c8bdd8 557 def _gen_fdt_nodes(base_node, node, depth, in_images):
6a0b5f8b 558 """Generate FDT nodes
dbe17c00
SG
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:
f584d44c 567 node (Node): Generator node to process
dbe17c00
SG
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:
f584d44c 573 firmware, fit_loadables = _process_firmware_prop(node)
dbe17c00
SG
574 # Generate nodes for each FDT
575 for seq, fdt_fname in enumerate(self._fdts):
01f467e2 576 node_name = node.name[1:].replace('SEQ', str(seq + 1))
dbe17c00
SG
577 fname = tools.get_input_filename(fdt_fname + '.dtb')
578 with fsw.add_node(node_name):
01f467e2 579 for pname, prop in node.props.items():
f584d44c
JK
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'
40c8bdd8
SG
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)
dbe17c00
SG
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))
b210661c
JK
601
602 for subnode in node.subnodes:
603 with fsw.add_node(subnode.name):
604 _add_node(node, depth + 1, subnode)
dbe17c00
SG
605 else:
606 if self._fdts is None:
607 if self._fit_list_prop:
5795497e
SG
608 self.Raise('Generator node requires '
609 f"'{self._fit_list_prop.value}' entry argument")
dbe17c00
SG
610 else:
611 self.Raise("Generator node requires 'fit,fdt-list' property")
612
00b3d53f 613 def _gen_split_elf(base_node, node, depth, segments, entry_addr):
40c8bdd8
SG
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)
00b3d53f 619 depth: Current node depth (0 is the base 'fit' node)
2f80c5ef
SG
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
40c8bdd8 625 """
2f80c5ef
SG
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}'")
40c8bdd8 643
00b3d53f
JK
644 for subnode in node.subnodes:
645 with fsw.add_node(subnode.name):
646 _add_node(node, depth + 1, subnode)
647
40c8bdd8 648 def _gen_node(base_node, node, depth, in_images, entry):
6a0b5f8b
SG
649 """Generate nodes from a template
650
226ce1d2
SG
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.
6a0b5f8b
SG
662
663 Args:
ce4e402a
SG
664 base_node (Node): Base Node of the FIT (with 'description'
665 property)
01f467e2 666 node (Node): Generator node to process
5795497e
SG
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
2f80c5ef
SG
670 entry (entry_Section): Entry for the section containing the
671 contents of this node
6a0b5f8b 672 """
01f467e2 673 oper = self._get_operation(base_node, node)
6a0b5f8b 674 if oper == OP_GEN_FDT_NODES:
40c8bdd8
SG
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
67a05017 680 missing_opt_list = []
40c8bdd8
SG
681 entry.ObtainContents()
682 entry.Pack(0)
67a05017
SG
683 entry.CheckMissing(missing_opt_list)
684 entry.CheckOptional(missing_opt_list)
40c8bdd8 685
2f80c5ef
SG
686 # If any pieces are missing, skip this. The missing entries will
687 # show an error
67a05017 688 if not missing_opt_list:
2f80c5ef
SG
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
00b3d53f 701 _gen_split_elf(base_node, node, depth, segments, entry_addr)
6a0b5f8b 702
38397d08
SG
703 def _add_node(base_node, depth, node):
704 """Add nodes to the output FIT
fdc34368
SG
705
706 Args:
5795497e
SG
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
fdc34368
SG
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
38397d08 715 image, so don't appear in the FIT
fdc34368 716 """
38397d08 717 # Copy over all the relevant properties
fdc34368 718 for pname, prop in node.props.items():
38397d08 719 _process_prop(pname, prop)
fdc34368
SG
720
721 rel_path = node.path[len(base_node.path):]
6cf9953b 722 in_images = rel_path.startswith('/images')
38397d08 723
6cf9953b 724 has_images = depth == 2 and in_images
fe05701b 725 if has_images:
e736878b
ANY
726 image_name = rel_path[len('/images/'):]
727 entry = self._priv_entries[image_name]
38397d08
SG
728 data = entry.GetData()
729 fsw.property('data', bytes(data))
fe05701b 730
fdc34368 731 for subnode in node.subnodes:
2337eca2 732 subnode_path = f'{rel_path}/{subnode.name}'
97fb8081 733 if has_images and not self.IsSpecialSubnode(subnode):
fe05701b
ANY
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
38397d08 736 # fsw.add_node() or _add_node() for it.
fe05701b 737 pass
fcc87efd 738 elif self.GetImage().generate and subnode.name.startswith('@'):
e736878b 739 entry = self._priv_entries.get(subnode.name)
40c8bdd8 740 _gen_node(base_node, subnode, depth, in_images, entry)
2337eca2
SG
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
e736878b 744 to_remove.append(subnode.name)
fdc34368
SG
745 else:
746 with fsw.add_node(subnode.name):
38397d08 747 _add_node(base_node, depth + 1, subnode)
fdc34368
SG
748
749 # Build a new tree with all nodes and properties starting from the
750 # entry node
751 fsw = libfdt.FdtSw()
109dbdf0 752 fsw.INC_SIZE = 65536
fdc34368 753 fsw.finish_reservemap()
2337eca2 754 to_remove = []
40c8bdd8 755 loadables = []
fdc34368 756 with fsw.add_node(''):
38397d08 757 _add_node(self._node, 0, self._node)
40c8bdd8 758 self._loadables = loadables
fdc34368
SG
759 fdt = fsw.as_fdt()
760
2337eca2
SG
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
fdc34368
SG
766 # Pack this new FDT and scan it so we can add the data later
767 fdt.pack()
38397d08 768 data = fdt.as_bytearray()
fdc34368 769 return data
fe05701b 770
73092220
ANY
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:
5795497e 778 image_pos (int): Position of this entry in the image
73092220 779 """
7caa372a
SG
780 if self.build_done:
781 return
73092220
ANY
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
e736878b
ANY
792 for image_name, section in self._entries.items():
793 path = f"/images/{image_name}"
73092220
ANY
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
5795497e 820 self.Raise(f'{path}: missing data properties')
73092220
ANY
821
822 section.SetOffsetSize(offset, size)
823 section.SetImagePos(self.image_pos)
824
ae9a4570
SG
825 def AddBintools(self, btools):
826 super().AddBintools(btools)
827 self.mkimage = self.AddBintool(btools, 'mkimage')
2337eca2
SG
828
829 def CheckMissing(self, missing_list):
dd4bdad4 830 # We must use our private entry list for this since generator nodes
2337eca2
SG
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)
7caa372a
SG
835
836 def CheckEntries(self):
837 pass
4023dc9c
IM
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:
5b34efe8 850 self.Raise("Missing tool: 'mkimage'")
4023dc9c
IM
851
852 data = tools.read_file(fname)
853 self.WriteData(data)