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 8import glob 9import os 10 11import libfdt 12import os 13 14from binman.entry import Entry, EntryArg 15from binman.etype.section import Entry_section 16from binman import elf 17from dtoc import fdt_util 18from dtoc.fdt import Fdt 19from u_boot_pylib import tools 20 21# Supported operations, with the fit,operation property 22OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2) 23OPERATIONS = { 24 'gen-fdt-nodes': OP_GEN_FDT_NODES, 25 'split-elf': OP_SPLIT_ELF, 26 } 27 28# pylint: disable=invalid-name 29class Entry_fit(Entry_section): 30 31 """Flat Image Tree (FIT) 32 33 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the 34 input provided. 35 36 Nodes for the FIT should be written out in the binman configuration just as 37 they would be in a file passed to mkimage. 38 39 For example, this creates an image containing a FIT with U-Boot SPL:: 40 41 binman { 42 fit { 43 description = "Test FIT"; 44 fit,fdt-list = "of-list"; 45 46 images { 47 kernel@1 { 48 description = "SPL"; 49 os = "u-boot"; 50 type = "rkspi"; 51 arch = "arm"; 52 compression = "none"; 53 load = <0>; 54 entry = <0>; 55 56 u-boot-spl { 57 }; 58 }; 59 }; 60 }; 61 }; 62 63 More complex setups can be created, with generated nodes, as described 64 below. 65 66 Properties (in the 'fit' node itself) 67 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 68 69 Special properties have a `fit,` prefix, indicating that they should be 70 processed but not included in the final FIT. 71 72 The top-level 'fit' node supports the following special properties: 73 74 fit,external-offset 75 Indicates that the contents of the FIT are external and provides the 76 external offset. This is passed to mkimage via the -E and -p flags. 77 78 fit,align 79 Indicates what alignment to use for the FIT and its external data, 80 and provides the alignment to use. This is passed to mkimage via 81 the -B flag. 82 83 fit,fdt-list 84 Indicates the entry argument which provides the list of device tree 85 files for the gen-fdt-nodes operation (as below). This is often 86 `of-list` meaning that `-a of-list="dtb1 dtb2..."` should be passed 87 to binman. 88 89 fit,fdt-list-val 90 As an alternative to fit,fdt-list the list of device tree files 91 can be provided in this property as a string list, e.g.:: 92 93 fit,fdt-list-val = "dtb1", "dtb2"; 94 95 fit,fdt-list-dir 96 As an alternative to fit,fdt-list the list of device tree files 97 can be provided as a directory. Each .dtb file in the directory is 98 processed, , e.g.:: 99 100 fit,fdt-list-dir = "arch/arm/dts"; 101 102 In this case the input directories are ignored and all devicetree 103 files must be in that directory. 104 105 fit,sign 106 Enable signing FIT images via mkimage as described in 107 verified-boot.rst. If the property is found, the private keys path 108 is detected among binman include directories and passed to mkimage 109 via -k flag. All the keys required for signing FIT must be 110 available at time of signing and must be located in single include 111 directory. 112 113 fit,encrypt 114 Enable data encryption in FIT images via mkimage. If the property 115 is found, the keys path is detected among binman include 116 directories and passed to mkimage via -k flag. All the keys 117 required for encrypting the FIT must be available at the time of 118 encrypting and must be located in a single include directory. 119 120 Substitutions 121 ~~~~~~~~~~~~~ 122 123 Node names and property values support a basic string-substitution feature. 124 Available substitutions for '@' nodes (and property values) are: 125 126 SEQ: 127 Sequence number of the generated fdt (1, 2, ...) 128 NAME 129 Name of the dtb as provided (i.e. without adding '.dtb') 130 131 The `default` property, if present, will be automatically set to the name 132 if of configuration whose devicetree matches the `default-dt` entry 133 argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`. 134 135 Available substitutions for property values in these nodes are: 136 137 DEFAULT-SEQ: 138 Sequence number of the default fdt, as provided by the 'default-dt' 139 entry argument 140 141 DEFAULT-NAME: 142 Name of the default fdt, as provided by the 'default-dt' entry argument 143 144 Available operations 145 ~~~~~~~~~~~~~~~~~~~~ 146 147 You can add an operation to an '@' node to indicate which operation is 148 required:: 149 150 @fdt-SEQ { 151 fit,operation = "gen-fdt-nodes"; 152 ... 153 }; 154 155 Available operations are: 156 157 gen-fdt-nodes 158 Generate FDT nodes as above. This is the default if there is no 159 `fit,operation` property. 160 161 split-elf 162 Split an ELF file into a separate node for each segment. 163 164 Generating nodes from an FDT list (gen-fdt-nodes) 165 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 166 167 U-Boot supports creating fdt and config nodes automatically. To do this, 168 pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells 169 binman that you want to generates nodes for two files: `file1.dtb` and 170 `file2.dtb`. The `fit,fdt-list` property (see above) indicates that 171 `of-list` should be used. If the property is missing you will get an error. 172 173 Then add a 'generator node', a node with a name starting with '@':: 174 175 images { 176 @fdt-SEQ { 177 description = "fdt-NAME"; 178 type = "flat_dt"; 179 compression = "none"; 180 }; 181 }; 182 183 This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two 184 files. All the properties you specify will be included in the node. This 185 node acts like a template to generate the nodes. The generator node itself 186 does not appear in the output - it is replaced with what binman generates. 187 A 'data' property is created with the contents of the FDT file. 188 189 You can create config nodes in a similar way:: 190 191 configurations { 192 default = "@config-DEFAULT-SEQ"; 193 @config-SEQ { 194 description = "NAME"; 195 firmware = "atf"; 196 loadables = "uboot"; 197 fdt = "fdt-SEQ"; 198 fit,compatible; // optional 199 }; 200 }; 201 202 This tells binman to create nodes `config-1` and `config-2`, i.e. a config 203 for each of your two files. 204 205 It is also possible to use NAME in the node names so that the FDT files name 206 will be used instead of the sequence number. This can be useful to identify 207 easily at runtime in U-Boot, the config to be used:: 208 209 configurations { 210 default = "@config-DEFAULT-NAME"; 211 @config-NAME { 212 description = "NAME"; 213 firmware = "atf"; 214 loadables = "uboot"; 215 fdt = "fdt-NAME"; 216 fit,compatible; // optional 217 }; 218 }; 219 220 Note that if no devicetree files are provided (with '-a of-list' as above) 221 then no nodes will be generated. 222 223 The 'fit,compatible' property (if present) is replaced with the compatible 224 string from the root node of the devicetree, so that things work correctly 225 with FIT's configuration-matching algortihm. 226 227 Dealing with phases 228 ~~~~~~~~~~~~~~~~~~~ 229 230 FIT can be used to load firmware. In this case it may be necessary to run 231 the devicetree for each model through fdtgrep to remove unwanted properties. 232 The 'fit,fdt-phase' property can be provided to indicate the phase for which 233 the devicetree is intended. 234 235 For example this indicates that the FDT should be processed for VPL:: 236 237 images { 238 @fdt-SEQ { 239 description = "fdt-NAME"; 240 type = "flat_dt"; 241 compression = "none"; 242 fit,fdt-phase = "vpl"; 243 }; 244 }; 245 246 Using this mechanism, it is possible to generate a FIT which can provide VPL 247 images for multiple models, with TPL selecting the correct model to use. The 248 same approach can of course be used for SPL images. 249 250 Note that the `of-spl-remove-props` entryarg can be used to indicate 251 additional properties to remove. It is often used to remove properties like 252 `clock-names` and `pinctrl-names` which are not needed in SPL builds. This 253 value is automatically passed to binman by the U-Boot build. 254 255 See :ref:`fdtgrep_filter` for more information. 256 257 Generating nodes from an ELF file (split-elf) 258 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 259 260 This uses the node as a template to generate multiple nodes. The following 261 special properties are available: 262 263 split-elf 264 Split an ELF file into a separate node for each segment. This uses the 265 node as a template to generate multiple nodes. The following special 266 properties are available: 267 268 fit,load 269 Generates a `load = <...>` property with the load address of the 270 segment 271 272 fit,entry 273 Generates a `entry = <...>` property with the entry address of the 274 ELF. This is only produced for the first entry 275 276 fit,data 277 Generates a `data = <...>` property with the contents of the segment 278 279 fit,firmware 280 Generates a `firmware = <...>` property. Provides a list of possible 281 nodes to be used as the `firmware` property value. The first valid 282 node is picked as the firmware. Any remaining valid nodes is 283 prepended to the `loadable` property generated by `fit,loadables` 284 285 fit,loadables 286 Generates a `loadable = <...>` property with a list of the generated 287 nodes (including all nodes if this operation is used multiple times) 288 289 290 Here is an example showing ATF, TEE and a device tree all combined:: 291 292 fit { 293 description = "test-desc"; 294 #address-cells = <1>; 295 fit,fdt-list = "of-list"; 296 297 images { 298 u-boot { 299 description = "U-Boot (64-bit)"; 300 type = "standalone"; 301 os = "U-Boot"; 302 arch = "arm64"; 303 compression = "none"; 304 load = <CONFIG_TEXT_BASE>; 305 u-boot-nodtb { 306 }; 307 }; 308 @fdt-SEQ { 309 description = "fdt-NAME.dtb"; 310 type = "flat_dt"; 311 compression = "none"; 312 }; 313 @atf-SEQ { 314 fit,operation = "split-elf"; 315 description = "ARM Trusted Firmware"; 316 type = "firmware"; 317 arch = "arm64"; 318 os = "arm-trusted-firmware"; 319 compression = "none"; 320 fit,load; 321 fit,entry; 322 fit,data; 323 324 atf-bl31 { 325 }; 326 hash { 327 algo = "sha256"; 328 }; 329 }; 330 331 @tee-SEQ { 332 fit,operation = "split-elf"; 333 description = "TEE"; 334 type = "tee"; 335 arch = "arm64"; 336 os = "tee"; 337 compression = "none"; 338 fit,load; 339 fit,entry; 340 fit,data; 341 342 tee-os { 343 }; 344 hash { 345 algo = "sha256"; 346 }; 347 }; 348 }; 349 350 configurations { 351 default = "@config-DEFAULT-SEQ"; 352 @config-SEQ { 353 description = "conf-NAME.dtb"; 354 fdt = "fdt-SEQ"; 355 fit,firmware = "atf-1", "u-boot"; 356 fit,loadables; 357 }; 358 }; 359 }; 360 361 If ATF-BL31 is available, this generates a node for each segment in the 362 ELF file, for example:: 363 364 images { 365 atf-1 { 366 data = <...contents of first segment...>; 367 data-offset = <0x00000000>; 368 entry = <0x00040000>; 369 load = <0x00040000>; 370 compression = "none"; 371 os = "arm-trusted-firmware"; 372 arch = "arm64"; 373 type = "firmware"; 374 description = "ARM Trusted Firmware"; 375 hash { 376 algo = "sha256"; 377 value = <...hash of first segment...>; 378 }; 379 }; 380 atf-2 { 381 data = <...contents of second segment...>; 382 load = <0xff3b0000>; 383 compression = "none"; 384 os = "arm-trusted-firmware"; 385 arch = "arm64"; 386 type = "firmware"; 387 description = "ARM Trusted Firmware"; 388 hash { 389 algo = "sha256"; 390 value = <...hash of second segment...>; 391 }; 392 }; 393 }; 394 395 The same applies for OP-TEE if that is available. 396 397 If each binary is not available, the relevant template node (@atf-SEQ or 398 @tee-SEQ) is removed from the output. 399 400 This also generates a `config-xxx` node for each device tree in `of-list`. 401 Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)` 402 so you can use `CONFIG_OF_LIST` to define that list. In this example it is 403 set up for `firefly-rk3399` with a single device tree and the default set 404 with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output 405 is:: 406 407 configurations { 408 default = "config-1"; 409 config-1 { 410 loadables = "u-boot", "atf-2", "atf-3", "tee-1", "tee-2"; 411 description = "rk3399-firefly.dtb"; 412 fdt = "fdt-1"; 413 firmware = "atf-1"; 414 }; 415 }; 416 417 U-Boot SPL can then load the firmware (ATF) and all the loadables (U-Boot 418 proper, ATF and TEE), then proceed with the boot. 419 """ 420 def __init__(self, section, etype, node): 421 """ 422 Members: 423 _fit (str): FIT file being built 424 _fit_props (list of str): 'fit,...' properties found in the 425 top-level node 426 _fdts (list of str): Filenames of .dtb files to process 427 _fdt_dir (str): Directory to scan to find .dtb files, or None 428 _fit_list_prop (str): Name of the EntryArg containing a list of .dtb 429 files 430 _fit_default_dt (str): Name of the EntryArg containing the default 431 .dtb file 432 _entries (dict of entries): from Entry_section: 433 key: relative path to entry Node (from the base of the FIT) 434 value: Entry_section object comprising the contents of this 435 node 436 _priv_entries (dict of entries): Internal copy of _entries which 437 includes 'generator' entries which are used to create the FIT, 438 but should not be processed as real entries. This is set up once 439 we have the entries 440 _loadables (list of str): List of generated split-elf nodes, each 441 a node name 442 _remove_props (list of str): Value of of-spl-remove-props EntryArg, 443 the list of properties to remove with fdtgrep 444 mkimage (Bintool): mkimage tool 445 fdtgrep (Bintool): fdtgrep tool 446 """ 447 super().__init__(section, etype, node) 448 self._fit = None 449 self._fit_props = {} 450 self._fdts = None 451 self._fdt_dir = None 452 self._fit_list_prop = None 453 self._fit_default_dt = None 454 self._priv_entries = {} 455 self._loadables = [] 456 self._remove_props = [] 457 props = self.GetEntryArgsOrProps( 458 [EntryArg('of-spl-remove-props', str)], required=False)[0] 459 if props: 460 self._remove_props = props.split() 461 self.mkimage = None 462 self.fdtgrep = None 463 self._fit_sign = None 464 465 def ReadNode(self): 466 super().ReadNode() 467 for pname, prop in self._node.props.items(): 468 if pname.startswith('fit,'): 469 self._fit_props[pname] = prop 470 self._fit_list_prop = self._fit_props.get('fit,fdt-list') 471 if self._fit_list_prop: 472 fdts = self.GetEntryArgsOrProps( 473 [EntryArg(self._fit_list_prop.value, str)])[0] 474 if fdts is not None: 475 self._fdts = fdts.split() 476 else: 477 self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir') 478 if self._fdt_dir: 479 indir = tools.get_input_filename(self._fdt_dir) 480 if indir: 481 tools.append_input_dirs(indir) 482 fdts = glob.glob('*.dtb', root_dir=indir) 483 self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)] 484 else: 485 self._fdts = fdt_util.GetStringList(self._node, 486 'fit,fdt-list-val') 487 488 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', 489 str)])[0] 490 491 def _get_operation(self, node): 492 """Get the operation referenced by a subnode 493 494 Args: 495 node (Node): Subnode (of the FIT) to check 496 497 Returns: 498 int: Operation to perform 499 500 Raises: 501 ValueError: Invalid operation name 502 """ 503 oper_name = node.props.get('fit,operation') 504 if not oper_name: 505 return OP_GEN_FDT_NODES 506 oper = OPERATIONS.get(oper_name.value) 507 if oper is None: 508 self._raise_subnode(node, f"Unknown operation '{oper_name.value}'") 509 return oper 510 511 def ReadEntries(self): 512 def _add_entries(base_node, depth, node): 513 """Add entries for any nodes that need them 514 515 Args: 516 base_node: Base Node of the FIT (with 'description' property) 517 depth: Current node depth (0 is the base 'fit' node) 518 node: Current node to process 519 520 Here we only need to provide binman entries which are used to define 521 the 'data' for each image. We create an entry_Section for each. 522 """ 523 rel_path = node.path[len(base_node.path):] 524 in_images = rel_path.startswith('/images') 525 has_images = depth == 2 and in_images 526 if has_images: 527 # This node is a FIT subimage node (e.g. "/images/kernel") 528 # containing content nodes. We collect the subimage nodes and 529 # section entries for them here to merge the content subnodes 530 # together and put the merged contents in the subimage node's 531 # 'data' property later. 532 entry = Entry.Create(self, node, etype='section') 533 entry.ReadNode() 534 # The hash subnodes here are for mkimage, not binman. 535 entry.SetUpdateHash(False) 536 image_name = rel_path[len('/images/'):] 537 self._entries[image_name] = entry 538 539 for subnode in node.subnodes: 540 _add_entries(base_node, depth + 1, subnode) 541 542 _add_entries(self._node, 0, self._node) 543 544 # Keep a copy of all entries, including generator entries, since those 545 # are removed from self._entries later. 546 self._priv_entries = dict(self._entries) 547 548 def _get_keys_dir(self, data): 549 """Detect private and encryption keys path among binman include directories 550 551 Args: 552 data: FIT image in binary format 553 554 Returns: 555 str: Single path containing all keys found or None 556 557 Raises: 558 ValueError: Filename 'rsa2048.key' not found in input path 559 ValueError: Multiple key paths found 560 ValueError: 'dir/rsa2048' is a path not a filename 561 """ 562 def _find_keys_dir(node): 563 for subnode in node.subnodes: 564 if (subnode.name.startswith('signature') or 565 subnode.name.startswith('cipher')): 566 hint = subnode.props['key-name-hint'].value 567 if '/' in hint: 568 self.Raise(f"'{hint}' is a path not a filename") 569 name = tools.get_input_filename( 570 f"{hint}.key" if subnode.name.startswith('signature') 571 else f"{hint}.bin") 572 path = os.path.dirname(name) 573 if path not in paths: 574 paths.append(path) 575 else: 576 _find_keys_dir(subnode) 577 return None 578 579 fdt = Fdt.FromData(data) 580 fdt.Scan() 581 582 paths = [] 583 584 _find_keys_dir(fdt.GetRoot()) 585 586 if len(paths) > 1: 587 self.Raise("multiple key paths found (%s)" % ",".join(paths)) 588 589 return paths[0] if len(paths) else None 590 591 def BuildSectionData(self, required): 592 """Build FIT entry contents 593 594 This adds the 'data' properties to the input ITB (Image-tree Binary) 595 then runs mkimage to process it. 596 597 Args: 598 required (bool): True if the data must be present, False if it is OK 599 to return None 600 601 Returns: 602 bytes: Contents of the section 603 """ 604 data = self._build_input() 605 uniq = self.GetUniqueName() 606 input_fname = tools.get_output_filename(f'{uniq}.itb') 607 output_fname = tools.get_output_filename(f'{uniq}.fit') 608 tools.write_file(input_fname, data) 609 tools.write_file(output_fname, data) 610 611 args = {} 612 ext_offset = self._fit_props.get('fit,external-offset') 613 if ext_offset is not None: 614 args = { 615 'external': True, 616 'pad': fdt_util.fdt32_to_cpu(ext_offset.value) 617 } 618 align = self._fit_props.get('fit,align') 619 if align is not None: 620 args.update({'align': fdt_util.fdt32_to_cpu(align.value)}) 621 if (self._fit_props.get('fit,sign') is not None or 622 self._fit_props.get('fit,encrypt') is not None): 623 args.update({'keys_dir': self._get_keys_dir(data)}) 624 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname, 625 **args) is None: 626 if not self.GetAllowMissing(): 627 self.Raise("Missing tool: 'mkimage'") 628 # Bintool is missing; just use empty data as the output 629 self.record_missing_bintool(self.mkimage) 630 return tools.get_bytes(0, 1024) 631 632 return tools.read_file(output_fname) 633 634 def _raise_subnode(self, node, msg): 635 """Raise an error with a paticular FIT subnode 636 637 Args: 638 node (Node): FIT subnode containing the error 639 msg (str): Message to report 640 641 Raises: 642 ValueError, as requested 643 """ 644 rel_path = node.path[len(self._node.path) + 1:] 645 self.Raise(f"subnode '{rel_path}': {msg}") 646 647 def _run_fdtgrep(self, infile, phase, outfile): 648 """Run fdtgrep to create the dtb for a phase 649 650 Args: 651 infile (str): Input filename containing the full FDT contents (with 652 all nodes and properties) 653 phase (str): Phase to generate for ('tpl', 'vpl', 'spl') 654 outfile (str): Output filename to write the grepped FDT contents to 655 (with only neceesary nodes and properties) 656 657 Returns: 658 str or bytes: Resulting stdout from fdtgrep 659 """ 660 return self.fdtgrep.create_for_phase(infile, phase, outfile, 661 self._remove_props) 662 663 def _build_input(self): 664 """Finish the FIT by adding the 'data' properties to it 665 666 Returns: 667 bytes: New fdt contents 668 """ 669 def _process_prop(pname, prop): 670 """Process special properties 671 672 Handles properties with generated values. At present the only 673 supported property is 'default', i.e. the default device tree in 674 the configurations node. 675 676 Args: 677 pname (str): Name of property 678 prop (Prop): Property to process 679 """ 680 if pname == 'default': 681 val = prop.value 682 # Handle the 'default' property 683 if val.startswith('@'): 684 if not self._fdts: 685 return 686 default_dt = self._fit_default_dt 687 if not default_dt: 688 self.Raise("Generated 'default' node requires default-dt entry argument") 689 if default_dt not in self._fdts: 690 if self._fdt_dir: 691 default_dt = os.path.basename(default_dt) 692 if default_dt not in self._fdts: 693 self.Raise( 694 f"default-dt entry argument '{self._fit_default_dt}' " 695 f"not found in fdt list: {', '.join(self._fdts)}") 696 seq = self._fdts.index(default_dt) 697 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) 698 val = val.replace('DEFAULT-NAME', self._fit_default_dt) 699 fsw.property_string(pname, val) 700 return 701 elif pname.startswith('fit,'): 702 # Ignore these, which are commands for binman to process 703 return 704 elif pname in ['offset', 'size', 'image-pos']: 705 # Don't add binman's calculated properties 706 return 707 fsw.property(pname, prop.bytes) 708 709 def _process_firmware_prop(node): 710 """Process optional fit,firmware property 711 712 Picks the first valid entry for use as the firmware, remaining valid 713 entries is prepended to loadables 714 715 Args: 716 node (Node): Generator node to process 717 718 Returns: 719 firmware (str): Firmware or None 720 result (list): List of remaining loadables 721 """ 722 val = fdt_util.GetStringList(node, 'fit,firmware') 723 if val is None: 724 return None, loadables 725 valid_entries = list(loadables) 726 for name, entry in self.GetEntries().items(): 727 missing = [] 728 entry.CheckMissing(missing) 729 entry.CheckOptional(missing) 730 if not missing: 731 valid_entries.append(name) 732 firmware = None 733 result = [] 734 for name in val: 735 if name in valid_entries: 736 if not firmware: 737 firmware = name 738 elif name not in result: 739 result.append(name) 740 for name in loadables: 741 if name != firmware and name not in result: 742 result.append(name) 743 return firmware, result 744 745 def _gen_fdt_nodes(node, depth, in_images): 746 """Generate FDT nodes 747 748 This creates one node for each member of self._fdts using the 749 provided template. If a property value contains 'NAME' it is 750 replaced with the filename of the FDT. If a property value contains 751 SEQ it is replaced with the node sequence number, where 1 is the 752 first. 753 754 Args: 755 node (Node): Generator node to process 756 depth: Current node depth (0 is the base 'fit' node) 757 in_images: True if this is inside the 'images' node, so that 758 'data' properties should be generated 759 """ 760 if self._fdts: 761 firmware, fit_loadables = _process_firmware_prop(node) 762 # Generate nodes for each FDT 763 for seq, fdt_fname in enumerate(self._fdts): 764 node_name = node.name[1:].replace('SEQ', str(seq + 1)) 765 node_name = node_name.replace('NAME', fdt_fname) 766 if self._fdt_dir: 767 fname = os.path.join(self._fdt_dir, fdt_fname + '.dtb') 768 else: 769 fname = tools.get_input_filename(fdt_fname + '.dtb') 770 fdt_phase = None 771 with fsw.add_node(node_name): 772 for pname, prop in node.props.items(): 773 if pname == 'fit,firmware': 774 if firmware: 775 fsw.property_string('firmware', firmware) 776 elif pname == 'fit,loadables': 777 val = '\0'.join(fit_loadables) + '\0' 778 fsw.property('loadables', val.encode('utf-8')) 779 elif pname == 'fit,operation': 780 pass 781 elif pname == 'fit,compatible': 782 fdt_phase = fdt_util.GetString(node, pname) 783 data = tools.read_file(fname) 784 fdt = Fdt.FromData(data) 785 fdt.Scan() 786 prop = fdt.GetRoot().props['compatible'] 787 fsw.property('compatible', prop.bytes) 788 elif pname == 'fit,fdt-phase': 789 fdt_phase = fdt_util.GetString(node, pname) 790 elif pname.startswith('fit,'): 791 self._raise_subnode( 792 node, f"Unknown directive '{pname}'") 793 else: 794 val = prop.bytes.replace( 795 b'NAME', tools.to_bytes(fdt_fname)) 796 val = val.replace( 797 b'SEQ', tools.to_bytes(str(seq + 1))) 798 fsw.property(pname, val) 799 800 # Add data for 'images' nodes (but not 'config') 801 if depth == 1 and in_images: 802 if fdt_phase: 803 leaf = os.path.basename(fdt_fname) 804 phase_fname = tools.get_output_filename( 805 f'{leaf}-{fdt_phase}.dtb') 806 self._run_fdtgrep(fname, fdt_phase, phase_fname) 807 data = tools.read_file(phase_fname) 808 else: 809 data = tools.read_file(fname) 810 fsw.property('data', data) 811 812 for subnode in node.subnodes: 813 with fsw.add_node(subnode.name): 814 _add_node(node, depth + 1, subnode) 815 else: 816 if self._fdts is None: 817 if self._fit_list_prop: 818 self.Raise('Generator node requires ' 819 f"'{self._fit_list_prop.value}' entry argument") 820 else: 821 self.Raise("Generator node requires 'fit,fdt-list' property") 822 823 def _gen_split_elf(node, depth, segments, entry_addr): 824 """Add nodes for the ELF file, one per group of contiguous segments 825 826 Args: 827 node (Node): Node to replace (in the FIT being built) 828 depth: Current node depth (0 is the base 'fit' node) 829 segments (list): list of segments, each: 830 int: Segment number (0 = first) 831 int: Start address of segment in memory 832 bytes: Contents of segment 833 entry_addr (int): entry address of ELF file 834 """ 835 for (seq, start, data) in segments: 836 node_name = node.name[1:].replace('SEQ', str(seq + 1)) 837 with fsw.add_node(node_name): 838 loadables.append(node_name) 839 for pname, prop in node.props.items(): 840 if not pname.startswith('fit,'): 841 fsw.property(pname, prop.bytes) 842 elif pname == 'fit,load': 843 fsw.property_u32('load', start) 844 elif pname == 'fit,entry': 845 if seq == 0: 846 fsw.property_u32('entry', entry_addr) 847 elif pname == 'fit,data': 848 fsw.property('data', bytes(data)) 849 elif pname != 'fit,operation': 850 self._raise_subnode( 851 node, f"Unknown directive '{pname}'") 852 853 for subnode in node.subnodes: 854 with fsw.add_node(subnode.name): 855 _add_node(node, depth + 1, subnode) 856 857 def _gen_node(node, depth, in_images, entry): 858 """Generate nodes from a template 859 860 This creates one or more nodes depending on the fit,operation being 861 used. 862 863 For OP_GEN_FDT_NODES it creates one node for each member of 864 self._fdts using the provided template. If a property value contains 865 'NAME' it is replaced with the filename of the FDT. If a property 866 value contains SEQ it is replaced with the node sequence number, 867 where 1 is the first. 868 869 For OP_SPLIT_ELF it emits one node for each section in the ELF file. 870 If the file is missing, nothing is generated. 871 872 Args: 873 node (Node): Generator node to process 874 depth (int): Current node depth (0 is the base 'fit' node) 875 in_images (bool): True if this is inside the 'images' node, so 876 that 'data' properties should be generated 877 entry (entry_Section): Entry for the section containing the 878 contents of this node 879 """ 880 oper = self._get_operation(node) 881 if oper == OP_GEN_FDT_NODES: 882 _gen_fdt_nodes(node, depth, in_images) 883 elif oper == OP_SPLIT_ELF: 884 # Entry_section.ObtainContents() either returns True or 885 # raises an exception. 886 missing_opt_list = [] 887 entry.ObtainContents() 888 entry.Pack(0) 889 entry.CheckMissing(missing_opt_list) 890 entry.CheckOptional(missing_opt_list) 891 892 # If any pieces are missing, skip this. The missing entries will 893 # show an error 894 if not missing_opt_list: 895 segs = entry.read_elf_segments() 896 if segs: 897 segments, entry_addr = segs 898 else: 899 elf_data = entry.GetData() 900 try: 901 segments, entry_addr = ( 902 elf.read_loadable_segments(elf_data)) 903 except ValueError as exc: 904 self._raise_subnode( 905 node, f'Failed to read ELF file: {str(exc)}') 906 907 _gen_split_elf(node, depth, segments, entry_addr) 908 909 def _add_node(base_node, depth, node): 910 """Add nodes to the output FIT 911 912 Args: 913 base_node (Node): Base Node of the FIT (with 'description' 914 property) 915 depth (int): Current node depth (0 is the base 'fit' node) 916 node (Node): Current node to process 917 918 There are two cases to deal with: 919 - hash and signature nodes which become part of the FIT 920 - binman entries which are used to define the 'data' for each 921 image, so don't appear in the FIT 922 """ 923 # Copy over all the relevant properties 924 for pname, prop in node.props.items(): 925 _process_prop(pname, prop) 926 927 rel_path = node.path[len(base_node.path):] 928 in_images = rel_path.startswith('/images') 929 930 has_images = depth == 2 and in_images 931 if has_images: 932 image_name = rel_path[len('/images/'):] 933 entry = self._priv_entries[image_name] 934 data = entry.GetData() 935 fsw.property('data', bytes(data)) 936 937 for subnode in node.subnodes: 938 if has_images and not self.IsSpecialSubnode(subnode): 939 # This subnode is a content node not meant to appear in 940 # the FIT (e.g. "/images/kernel/u-boot"), so don't call 941 # fsw.add_node() or _add_node() for it. 942 pass 943 elif self.GetImage().generate and subnode.name.startswith('@'): 944 entry = self._priv_entries.get(subnode.name) 945 _gen_node(subnode, depth, in_images, entry) 946 # This is a generator (template) entry, so remove it from 947 # the list of entries used by PackEntries(), etc. Otherwise 948 # it will appear in the binman output 949 to_remove.append(subnode.name) 950 else: 951 with fsw.add_node(subnode.name): 952 _add_node(base_node, depth + 1, subnode) 953 954 # Build a new tree with all nodes and properties starting from the 955 # entry node 956 fsw = libfdt.FdtSw() 957 fsw.INC_SIZE = 65536 958 fsw.finish_reservemap() 959 to_remove = [] 960 loadables = [] 961 with fsw.add_node(''): 962 _add_node(self._node, 0, self._node) 963 self._loadables = loadables 964 fdt = fsw.as_fdt() 965 966 # Remove generator entries from the main list 967 for path in to_remove: 968 if path in self._entries: 969 del self._entries[path] 970 971 # Pack this new FDT and scan it so we can add the data later 972 fdt.pack() 973 data = fdt.as_bytearray() 974 return data 975 976 def SetImagePos(self, image_pos): 977 """Set the position in the image 978 979 This sets each subentry's offsets, sizes and positions-in-image 980 according to where they ended up in the packed FIT file. 981 982 Args: 983 image_pos (int): Position of this entry in the image 984 """ 985 if self.build_done: 986 return 987 988 # Skip the section processing, since we do that below. Just call the 989 # entry method 990 Entry.SetImagePos(self, image_pos) 991 992 # If mkimage is missing we'll have empty data, 993 # which will cause a FDT_ERR_BADMAGIC error 994 if self.mkimage in self.missing_bintools: 995 return 996 997 fdt = Fdt.FromData(self.GetData()) 998 fdt.Scan() 999 1000 for image_name, entry in self._entries.items(): 1001 path = f"/images/{image_name}" 1002 node = fdt.GetNode(path) 1003 1004 data_prop = node.props.get("data") 1005 data_pos = fdt_util.GetInt(node, "data-position") 1006 data_offset = fdt_util.GetInt(node, "data-offset") 1007 data_size = fdt_util.GetInt(node, "data-size") 1008 1009 # Contents are inside the FIT 1010 if data_prop is not None: 1011 # GetOffset() returns offset of a fdt_property struct, 1012 # which has 3 fdt32_t members before the actual data. 1013 offset = data_prop.GetOffset() + 12 1014 size = len(data_prop.bytes) 1015 1016 # External offset from the base of the FIT 1017 elif data_pos is not None: 1018 offset = data_pos 1019 size = data_size 1020 1021 # External offset from the end of the FIT, not used in binman 1022 elif data_offset is not None: # pragma: no cover 1023 offset = fdt.GetFdtObj().totalsize() + data_offset 1024 size = data_size 1025 1026 # This should never happen 1027 else: # pragma: no cover 1028 offset = None 1029 size = None 1030 self.Raise(f'{path}: missing data properties') 1031 1032 entry.SetOffsetSize(offset, size) 1033 entry.SetImagePos(image_pos + self.offset) 1034 1035 def AddBintools(self, btools): 1036 super().AddBintools(btools) 1037 self.mkimage = self.AddBintool(btools, 'mkimage') 1038 self.fdtgrep = self.AddBintool(btools, 'fdtgrep') 1039 1040 def CheckMissing(self, missing_list): 1041 # We must use our private entry list for this since generator nodes 1042 # which are removed from self._entries will otherwise not show up as 1043 # missing 1044 for entry in self._priv_entries.values(): 1045 entry.CheckMissing(missing_list) 1046 1047 def CheckOptional(self, optional_list): 1048 # We must use our private entry list for this since generator nodes 1049 # which are removed from self._entries will otherwise not show up as 1050 # optional 1051 for entry in self._priv_entries.values(): 1052 entry.CheckOptional(optional_list) 1053 1054 def CheckEntries(self): 1055 pass 1056 1057 def UpdateSignatures(self, privatekey_fname, algo, input_fname): 1058 uniq = self.GetUniqueName() 1059 args = [ '-G', privatekey_fname, '-r', '-o', algo, '-F' ] 1060 if input_fname: 1061 fname = input_fname 1062 else: 1063 fname = tools.get_output_filename(f'{uniq}.fit') 1064 tools.write_file(fname, self.GetData()) 1065 args.append(fname) 1066 1067 if self.mkimage.run_cmd(*args) is None: 1068 self.Raise("Missing tool: 'mkimage'") 1069 1070 data = tools.read_file(fname) 1071 self.WriteData(data) 1072