]>
Commit | Line | Data |
---|---|---|
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 |
8 | import libfdt |
9 | ||
6cf9953b | 10 | from binman.entry import Entry, EntryArg |
f3078d4e | 11 | from binman.etype.section import Entry_section |
40c8bdd8 | 12 | from binman import elf |
fdc34368 SG |
13 | from dtoc import fdt_util |
14 | from dtoc.fdt import Fdt | |
4583c002 | 15 | from u_boot_pylib import tools |
fdc34368 | 16 | |
6a0b5f8b | 17 | # Supported operations, with the fit,operation property |
40c8bdd8 | 18 | OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2) |
6a0b5f8b SG |
19 | OPERATIONS = { |
20 | 'gen-fdt-nodes': OP_GEN_FDT_NODES, | |
40c8bdd8 | 21 | 'split-elf': OP_SPLIT_ELF, |
6a0b5f8b SG |
22 | } |
23 | ||
f3078d4e | 24 | class 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) |