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 libfdt 9 10from binman.entry import Entry, EntryArg 11from binman.etype.section import Entry_section 12from binman import elf 13from dtoc import fdt_util 14from dtoc.fdt import Fdt 15from u_boot_pylib import tools 16 17# Supported operations, with the fit,operation property 18OP_GEN_FDT_NODES, OP_SPLIT_ELF = range(2) 19OPERATIONS = { 20 'gen-fdt-nodes': OP_GEN_FDT_NODES, 21 'split-elf': OP_SPLIT_ELF, 22 } 23 24class Entry_fit(Entry_section): 25 26 """Flat Image Tree (FIT) 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 34 For example, this creates an image containing a FIT with U-Boot SPL:: 35 36 binman { 37 fit { 38 description = "Test FIT"; 39 fit,fdt-list = "of-list"; 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 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 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 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 fit,fdt-list-val 85 As an alternative to fit,fdt-list the list of device tree files 86 can be provided in this property as a string list, e.g.:: 87 88 fit,fdt-list-val = "dtb1", "dtb2"; 89 90 Substitutions 91 ~~~~~~~~~~~~~ 92 93 Node names and property values support a basic string-substitution feature. 94 Available substitutions for '@' nodes (and property values) are: 95 96 SEQ: 97 Sequence number of the generated fdt (1, 2, ...) 98 NAME 99 Name of the dtb as provided (i.e. without adding '.dtb') 100 101 The `default` property, if present, will be automatically set to the name 102 if of configuration whose devicetree matches the `default-dt` entry 103 argument, e.g. with `-a default-dt=sun50i-a64-pine64-lts`. 104 105 Available substitutions for property values in these nodes are: 106 107 DEFAULT-SEQ: 108 Sequence number of the default fdt, as provided by the 'default-dt' 109 entry argument 110 111 Available operations 112 ~~~~~~~~~~~~~~~~~~~~ 113 114 You can add an operation to an '@' node to indicate which operation is 115 required:: 116 117 @fdt-SEQ { 118 fit,operation = "gen-fdt-nodes"; 119 ... 120 }; 121 122 Available operations are: 123 124 gen-fdt-nodes 125 Generate FDT nodes as above. This is the default if there is no 126 `fit,operation` property. 127 128 split-elf 129 Split an ELF file into a separate node for each segment. 130 131 Generating nodes from an FDT list (gen-fdt-nodes) 132 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 134 U-Boot supports creating fdt and config nodes automatically. To do this, 135 pass an `of-list` property (e.g. `-a of-list=file1 file2`). This tells 136 binman that you want to generates nodes for two files: `file1.dtb` and 137 `file2.dtb`. The `fit,fdt-list` property (see above) indicates that 138 `of-list` should be used. If the property is missing you will get an error. 139 140 Then add a 'generator node', a node with a name starting with '@':: 141 142 images { 143 @fdt-SEQ { 144 description = "fdt-NAME"; 145 type = "flat_dt"; 146 compression = "none"; 147 }; 148 }; 149 150 This tells binman to create nodes `fdt-1` and `fdt-2` for each of your two 151 files. All the properties you specify will be included in the node. This 152 node acts like a template to generate the nodes. The generator node itself 153 does not appear in the output - it is replaced with what binman generates. 154 A 'data' property is created with the contents of the FDT file. 155 156 You can create config nodes in a similar way:: 157 158 configurations { 159 default = "@config-DEFAULT-SEQ"; 160 @config-SEQ { 161 description = "NAME"; 162 firmware = "atf"; 163 loadables = "uboot"; 164 fdt = "fdt-SEQ"; 165 }; 166 }; 167 168 This tells binman to create nodes `config-1` and `config-2`, i.e. a config 169 for each of your two files. 170 171 Note that if no devicetree files are provided (with '-a of-list' as above) 172 then no nodes will be generated. 173 174 Generating nodes from an ELF file (split-elf) 175 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 176 177 This uses the node as a template to generate multiple nodes. The following 178 special properties are available: 179 180 split-elf 181 Split an ELF file into a separate node for each segment. This uses the 182 node as a template to generate multiple nodes. The following special 183 properties are available: 184 185 fit,load 186 Generates a `load = <...>` property with the load address of the 187 segment 188 189 fit,entry 190 Generates a `entry = <...>` property with the entry address of the 191 ELF. This is only produced for the first entry 192 193 fit,data 194 Generates a `data = <...>` property with the contents of the segment 195 196 fit,firmware 197 Generates a `firmware = <...>` property. Provides a list of possible 198 nodes to be used as the `firmware` property value. The first valid 199 node is picked as the firmware. Any remaining valid nodes is 200 prepended to the `loadable` property generated by `fit,loadables` 201 202 fit,loadables 203 Generates a `loadable = <...>` property with a list of the generated 204 nodes (including all nodes if this operation is used multiple times) 205 206 207 Here is an example showing ATF, TEE and a device tree all combined:: 208 209 fit { 210 description = "test-desc"; 211 #address-cells = <1>; 212 fit,fdt-list = "of-list"; 213 214 images { 215 u-boot { 216 description = "U-Boot (64-bit)"; 217 type = "standalone"; 218 os = "U-Boot"; 219 arch = "arm64"; 220 compression = "none"; 221 load = <CONFIG_TEXT_BASE>; 222 u-boot-nodtb { 223 }; 224 }; 225 @fdt-SEQ { 226 description = "fdt-NAME.dtb"; 227 type = "flat_dt"; 228 compression = "none"; 229 }; 230 @atf-SEQ { 231 fit,operation = "split-elf"; 232 description = "ARM Trusted Firmware"; 233 type = "firmware"; 234 arch = "arm64"; 235 os = "arm-trusted-firmware"; 236 compression = "none"; 237 fit,load; 238 fit,entry; 239 fit,data; 240 241 atf-bl31 { 242 }; 243 hash { 244 algo = "sha256"; 245 }; 246 }; 247 248 @tee-SEQ { 249 fit,operation = "split-elf"; 250 description = "TEE"; 251 type = "tee"; 252 arch = "arm64"; 253 os = "tee"; 254 compression = "none"; 255 fit,load; 256 fit,entry; 257 fit,data; 258 259 tee-os { 260 }; 261 hash { 262 algo = "sha256"; 263 }; 264 }; 265 }; 266 267 configurations { 268 default = "@config-DEFAULT-SEQ"; 269 @config-SEQ { 270 description = "conf-NAME.dtb"; 271 fdt = "fdt-SEQ"; 272 fit,firmware = "atf-1", "u-boot"; 273 fit,loadables; 274 }; 275 }; 276 }; 277 278 If ATF-BL31 is available, this generates a node for each segment in the 279 ELF file, for example:: 280 281 images { 282 atf-1 { 283 data = <...contents of first segment...>; 284 data-offset = <0x00000000>; 285 entry = <0x00040000>; 286 load = <0x00040000>; 287 compression = "none"; 288 os = "arm-trusted-firmware"; 289 arch = "arm64"; 290 type = "firmware"; 291 description = "ARM Trusted Firmware"; 292 hash { 293 algo = "sha256"; 294 value = <...hash of first segment...>; 295 }; 296 }; 297 atf-2 { 298 data = <...contents of second segment...>; 299 load = <0xff3b0000>; 300 compression = "none"; 301 os = "arm-trusted-firmware"; 302 arch = "arm64"; 303 type = "firmware"; 304 description = "ARM Trusted Firmware"; 305 hash { 306 algo = "sha256"; 307 value = <...hash of second segment...>; 308 }; 309 }; 310 }; 311 312 The same applies for OP-TEE if that is available. 313 314 If each binary is not available, the relevant template node (@atf-SEQ or 315 @tee-SEQ) is removed from the output. 316 317 This also generates a `config-xxx` node for each device tree in `of-list`. 318 Note that the U-Boot build system uses `-a of-list=$(CONFIG_OF_LIST)` 319 so you can use `CONFIG_OF_LIST` to define that list. In this example it is 320 set up for `firefly-rk3399` with a single device tree and the default set 321 with `-a default-dt=$(CONFIG_DEFAULT_DEVICE_TREE)`, so the resulting output 322 is:: 323 324 configurations { 325 default = "config-1"; 326 config-1 { 327 loadables = "u-boot", "atf-2", "atf-3", "tee-1", "tee-2"; 328 description = "rk3399-firefly.dtb"; 329 fdt = "fdt-1"; 330 firmware = "atf-1"; 331 }; 332 }; 333 334 U-Boot SPL can then load the firmware (ATF) and all the loadables (U-Boot 335 proper, ATF and TEE), then proceed with the boot. 336 """ 337 def __init__(self, section, etype, node): 338 """ 339 Members: 340 _fit: FIT file being built 341 _entries: dict from Entry_section: 342 key: relative path to entry Node (from the base of the FIT) 343 value: Entry_section object comprising the contents of this 344 node 345 _priv_entries: Internal copy of _entries which includes 'generator' 346 entries which are used to create the FIT, but should not be 347 processed as real entries. This is set up once we have the 348 entries 349 _loadables: List of generated split-elf nodes, each a node name 350 """ 351 super().__init__(section, etype, node) 352 self._fit = None 353 self._fit_props = {} 354 self._fdts = None 355 self.mkimage = None 356 self._priv_entries = {} 357 self._loadables = [] 358 359 def ReadNode(self): 360 super().ReadNode() 361 for pname, prop in self._node.props.items(): 362 if pname.startswith('fit,'): 363 self._fit_props[pname] = prop 364 self._fit_list_prop = self._fit_props.get('fit,fdt-list') 365 if self._fit_list_prop: 366 fdts, = self.GetEntryArgsOrProps( 367 [EntryArg(self._fit_list_prop.value, str)]) 368 if fdts is not None: 369 self._fdts = fdts.split() 370 else: 371 self._fdts = fdt_util.GetStringList(self._node, 'fit,fdt-list-val') 372 373 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', 374 str)])[0] 375 376 def _get_operation(self, base_node, node): 377 """Get the operation referenced by a subnode 378 379 Args: 380 node (Node): Subnode (of the FIT) to check 381 382 Returns: 383 int: Operation to perform 384 385 Raises: 386 ValueError: Invalid operation name 387 """ 388 oper_name = node.props.get('fit,operation') 389 if not oper_name: 390 return OP_GEN_FDT_NODES 391 oper = OPERATIONS.get(oper_name.value) 392 if oper is None: 393 self._raise_subnode(node, f"Unknown operation '{oper_name.value}'") 394 return oper 395 396 def ReadEntries(self): 397 def _add_entries(base_node, depth, node): 398 """Add entries for any nodes that need them 399 400 Args: 401 base_node: Base Node of the FIT (with 'description' property) 402 depth: Current node depth (0 is the base 'fit' node) 403 node: Current node to process 404 405 Here we only need to provide binman entries which are used to define 406 the 'data' for each image. We create an entry_Section for each. 407 """ 408 rel_path = node.path[len(base_node.path):] 409 in_images = rel_path.startswith('/images') 410 has_images = depth == 2 and in_images 411 if has_images: 412 # This node is a FIT subimage node (e.g. "/images/kernel") 413 # containing content nodes. We collect the subimage nodes and 414 # section entries for them here to merge the content subnodes 415 # together and put the merged contents in the subimage node's 416 # 'data' property later. 417 entry = Entry.Create(self, node, etype='section') 418 entry.ReadNode() 419 # The hash subnodes here are for mkimage, not binman. 420 entry.SetUpdateHash(False) 421 image_name = rel_path[len('/images/'):] 422 self._entries[image_name] = entry 423 424 for subnode in node.subnodes: 425 _add_entries(base_node, depth + 1, subnode) 426 427 _add_entries(self._node, 0, self._node) 428 429 # Keep a copy of all entries, including generator entries, since those 430 # are removed from self._entries later. 431 self._priv_entries = dict(self._entries) 432 433 def BuildSectionData(self, required): 434 """Build FIT entry contents 435 436 This adds the 'data' properties to the input ITB (Image-tree Binary) 437 then runs mkimage to process it. 438 439 Args: 440 required (bool): True if the data must be present, False if it is OK 441 to return None 442 443 Returns: 444 bytes: Contents of the section 445 """ 446 data = self._build_input() 447 uniq = self.GetUniqueName() 448 input_fname = tools.get_output_filename(f'{uniq}.itb') 449 output_fname = tools.get_output_filename(f'{uniq}.fit') 450 tools.write_file(input_fname, data) 451 tools.write_file(output_fname, data) 452 453 args = {} 454 ext_offset = self._fit_props.get('fit,external-offset') 455 if ext_offset is not None: 456 args = { 457 'external': True, 458 'pad': fdt_util.fdt32_to_cpu(ext_offset.value) 459 } 460 align = self._fit_props.get('fit,align') 461 if align is not None: 462 args.update({'align': fdt_util.fdt32_to_cpu(align.value)}) 463 if self.mkimage.run(reset_timestamp=True, output_fname=output_fname, 464 **args) is None: 465 if not self.GetAllowMissing(): 466 self.Raise("Missing tool: 'mkimage'") 467 # Bintool is missing; just use empty data as the output 468 self.record_missing_bintool(self.mkimage) 469 return tools.get_bytes(0, 1024) 470 471 return tools.read_file(output_fname) 472 473 def _raise_subnode(self, node, msg): 474 """Raise an error with a paticular FIT subnode 475 476 Args: 477 node (Node): FIT subnode containing the error 478 msg (str): Message to report 479 480 Raises: 481 ValueError, as requested 482 """ 483 rel_path = node.path[len(self._node.path) + 1:] 484 self.Raise(f"subnode '{rel_path}': {msg}") 485 486 def _build_input(self): 487 """Finish the FIT by adding the 'data' properties to it 488 489 Arguments: 490 fdt: FIT to update 491 492 Returns: 493 bytes: New fdt contents 494 """ 495 def _process_prop(pname, prop): 496 """Process special properties 497 498 Handles properties with generated values. At present the only 499 supported property is 'default', i.e. the default device tree in 500 the configurations node. 501 502 Args: 503 pname (str): Name of property 504 prop (Prop): Property to process 505 """ 506 if pname == 'default': 507 val = prop.value 508 # Handle the 'default' property 509 if val.startswith('@'): 510 if not self._fdts: 511 return 512 if not self._fit_default_dt: 513 self.Raise("Generated 'default' node requires default-dt entry argument") 514 if self._fit_default_dt not in self._fdts: 515 self.Raise( 516 f"default-dt entry argument '{self._fit_default_dt}' " 517 f"not found in fdt list: {', '.join(self._fdts)}") 518 seq = self._fdts.index(self._fit_default_dt) 519 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) 520 fsw.property_string(pname, val) 521 return 522 elif pname.startswith('fit,'): 523 # Ignore these, which are commands for binman to process 524 return 525 elif pname in ['offset', 'size', 'image-pos']: 526 # Don't add binman's calculated properties 527 return 528 fsw.property(pname, prop.bytes) 529 530 def _process_firmware_prop(node): 531 """Process optional fit,firmware property 532 533 Picks the first valid entry for use as the firmware, remaining valid 534 entries is prepended to loadables 535 536 Args: 537 node (Node): Generator node to process 538 539 Returns: 540 firmware (str): Firmware or None 541 result (list): List of remaining loadables 542 """ 543 val = fdt_util.GetStringList(node, 'fit,firmware') 544 if val is None: 545 return None, self._loadables 546 valid_entries = list(self._loadables) 547 for name, entry in self.GetEntries().items(): 548 missing = [] 549 entry.CheckMissing(missing) 550 entry.CheckOptional(missing) 551 if not missing: 552 valid_entries.append(name) 553 firmware = None 554 result = [] 555 for name in val: 556 if name in valid_entries: 557 if not firmware: 558 firmware = name 559 elif name not in result: 560 result.append(name) 561 for name in self._loadables: 562 if name != firmware and name not in result: 563 result.append(name) 564 return firmware, result 565 566 def _gen_fdt_nodes(base_node, node, depth, in_images): 567 """Generate FDT nodes 568 569 This creates one node for each member of self._fdts using the 570 provided template. If a property value contains 'NAME' it is 571 replaced with the filename of the FDT. If a property value contains 572 SEQ it is replaced with the node sequence number, where 1 is the 573 first. 574 575 Args: 576 node (Node): Generator node to process 577 depth: Current node depth (0 is the base 'fit' node) 578 in_images: True if this is inside the 'images' node, so that 579 'data' properties should be generated 580 """ 581 if self._fdts: 582 firmware, fit_loadables = _process_firmware_prop(node) 583 # Generate nodes for each FDT 584 for seq, fdt_fname in enumerate(self._fdts): 585 node_name = node.name[1:].replace('SEQ', str(seq + 1)) 586 fname = tools.get_input_filename(fdt_fname + '.dtb') 587 with fsw.add_node(node_name): 588 for pname, prop in node.props.items(): 589 if pname == 'fit,firmware': 590 if firmware: 591 fsw.property_string('firmware', firmware) 592 elif pname == 'fit,loadables': 593 val = '\0'.join(fit_loadables) + '\0' 594 fsw.property('loadables', val.encode('utf-8')) 595 elif pname == 'fit,operation': 596 pass 597 elif pname.startswith('fit,'): 598 self._raise_subnode( 599 node, f"Unknown directive '{pname}'") 600 else: 601 val = prop.bytes.replace( 602 b'NAME', tools.to_bytes(fdt_fname)) 603 val = val.replace( 604 b'SEQ', tools.to_bytes(str(seq + 1))) 605 fsw.property(pname, val) 606 607 # Add data for 'images' nodes (but not 'config') 608 if depth == 1 and in_images: 609 fsw.property('data', tools.read_file(fname)) 610 611 for subnode in node.subnodes: 612 with fsw.add_node(subnode.name): 613 _add_node(node, depth + 1, subnode) 614 else: 615 if self._fdts is None: 616 if self._fit_list_prop: 617 self.Raise('Generator node requires ' 618 f"'{self._fit_list_prop.value}' entry argument") 619 else: 620 self.Raise("Generator node requires 'fit,fdt-list' property") 621 622 def _gen_split_elf(base_node, node, depth, segments, entry_addr): 623 """Add nodes for the ELF file, one per group of contiguous segments 624 625 Args: 626 base_node (Node): Template node from the binman definition 627 node (Node): Node to replace (in the FIT being built) 628 depth: Current node depth (0 is the base 'fit' node) 629 segments (list): list of segments, each: 630 int: Segment number (0 = first) 631 int: Start address of segment in memory 632 bytes: Contents of segment 633 entry_addr (int): entry address of ELF file 634 """ 635 for (seq, start, data) in segments: 636 node_name = node.name[1:].replace('SEQ', str(seq + 1)) 637 with fsw.add_node(node_name): 638 loadables.append(node_name) 639 for pname, prop in node.props.items(): 640 if not pname.startswith('fit,'): 641 fsw.property(pname, prop.bytes) 642 elif pname == 'fit,load': 643 fsw.property_u32('load', start) 644 elif pname == 'fit,entry': 645 if seq == 0: 646 fsw.property_u32('entry', entry_addr) 647 elif pname == 'fit,data': 648 fsw.property('data', bytes(data)) 649 elif pname != 'fit,operation': 650 self._raise_subnode( 651 node, f"Unknown directive '{pname}'") 652 653 for subnode in node.subnodes: 654 with fsw.add_node(subnode.name): 655 _add_node(node, depth + 1, subnode) 656 657 def _gen_node(base_node, node, depth, in_images, entry): 658 """Generate nodes from a template 659 660 This creates one or more nodes depending on the fit,operation being 661 used. 662 663 For OP_GEN_FDT_NODES it creates one node for each member of 664 self._fdts using the provided template. If a property value contains 665 'NAME' it is replaced with the filename of the FDT. If a property 666 value contains SEQ it is replaced with the node sequence number, 667 where 1 is the first. 668 669 For OP_SPLIT_ELF it emits one node for each section in the ELF file. 670 If the file is missing, nothing is generated. 671 672 Args: 673 base_node (Node): Base Node of the FIT (with 'description' 674 property) 675 node (Node): Generator node to process 676 depth (int): Current node depth (0 is the base 'fit' node) 677 in_images (bool): True if this is inside the 'images' node, so 678 that 'data' properties should be generated 679 entry (entry_Section): Entry for the section containing the 680 contents of this node 681 """ 682 oper = self._get_operation(base_node, node) 683 if oper == OP_GEN_FDT_NODES: 684 _gen_fdt_nodes(base_node, node, depth, in_images) 685 elif oper == OP_SPLIT_ELF: 686 # Entry_section.ObtainContents() either returns True or 687 # raises an exception. 688 data = None 689 missing_opt_list = [] 690 entry.ObtainContents() 691 entry.Pack(0) 692 entry.CheckMissing(missing_opt_list) 693 entry.CheckOptional(missing_opt_list) 694 695 # If any pieces are missing, skip this. The missing entries will 696 # show an error 697 if not missing_opt_list: 698 segs = entry.read_elf_segments() 699 if segs: 700 segments, entry_addr = segs 701 else: 702 elf_data = entry.GetData() 703 try: 704 segments, entry_addr = ( 705 elf.read_loadable_segments(elf_data)) 706 except ValueError as exc: 707 self._raise_subnode( 708 node, f'Failed to read ELF file: {str(exc)}') 709 710 _gen_split_elf(base_node, node, depth, segments, entry_addr) 711 712 def _add_node(base_node, depth, node): 713 """Add nodes to the output FIT 714 715 Args: 716 base_node (Node): Base Node of the FIT (with 'description' 717 property) 718 depth (int): Current node depth (0 is the base 'fit' node) 719 node (Node): Current node to process 720 721 There are two cases to deal with: 722 - hash and signature nodes which become part of the FIT 723 - binman entries which are used to define the 'data' for each 724 image, so don't appear in the FIT 725 """ 726 # Copy over all the relevant properties 727 for pname, prop in node.props.items(): 728 _process_prop(pname, prop) 729 730 rel_path = node.path[len(base_node.path):] 731 in_images = rel_path.startswith('/images') 732 733 has_images = depth == 2 and in_images 734 if has_images: 735 image_name = rel_path[len('/images/'):] 736 entry = self._priv_entries[image_name] 737 data = entry.GetData() 738 fsw.property('data', bytes(data)) 739 740 for subnode in node.subnodes: 741 subnode_path = f'{rel_path}/{subnode.name}' 742 if has_images and not self.IsSpecialSubnode(subnode): 743 # This subnode is a content node not meant to appear in 744 # the FIT (e.g. "/images/kernel/u-boot"), so don't call 745 # fsw.add_node() or _add_node() for it. 746 pass 747 elif self.GetImage().generate and subnode.name.startswith('@'): 748 entry = self._priv_entries.get(subnode.name) 749 _gen_node(base_node, subnode, depth, in_images, entry) 750 # This is a generator (template) entry, so remove it from 751 # the list of entries used by PackEntries(), etc. Otherwise 752 # it will appear in the binman output 753 to_remove.append(subnode.name) 754 else: 755 with fsw.add_node(subnode.name): 756 _add_node(base_node, depth + 1, subnode) 757 758 # Build a new tree with all nodes and properties starting from the 759 # entry node 760 fsw = libfdt.FdtSw() 761 fsw.INC_SIZE = 65536 762 fsw.finish_reservemap() 763 to_remove = [] 764 loadables = [] 765 with fsw.add_node(''): 766 _add_node(self._node, 0, self._node) 767 self._loadables = loadables 768 fdt = fsw.as_fdt() 769 770 # Remove generator entries from the main list 771 for path in to_remove: 772 if path in self._entries: 773 del self._entries[path] 774 775 # Pack this new FDT and scan it so we can add the data later 776 fdt.pack() 777 data = fdt.as_bytearray() 778 return data 779 780 def SetImagePos(self, image_pos): 781 """Set the position in the image 782 783 This sets each subentry's offsets, sizes and positions-in-image 784 according to where they ended up in the packed FIT file. 785 786 Args: 787 image_pos (int): Position of this entry in the image 788 """ 789 if self.build_done: 790 return 791 super().SetImagePos(image_pos) 792 793 # If mkimage is missing we'll have empty data, 794 # which will cause a FDT_ERR_BADMAGIC error 795 if self.mkimage in self.missing_bintools: 796 return 797 798 fdt = Fdt.FromData(self.GetData()) 799 fdt.Scan() 800 801 for image_name, section in self._entries.items(): 802 path = f"/images/{image_name}" 803 node = fdt.GetNode(path) 804 805 data_prop = node.props.get("data") 806 data_pos = fdt_util.GetInt(node, "data-position") 807 data_offset = fdt_util.GetInt(node, "data-offset") 808 data_size = fdt_util.GetInt(node, "data-size") 809 810 # Contents are inside the FIT 811 if data_prop is not None: 812 # GetOffset() returns offset of a fdt_property struct, 813 # which has 3 fdt32_t members before the actual data. 814 offset = data_prop.GetOffset() + 12 815 size = len(data_prop.bytes) 816 817 # External offset from the base of the FIT 818 elif data_pos is not None: 819 offset = data_pos 820 size = data_size 821 822 # External offset from the end of the FIT, not used in binman 823 elif data_offset is not None: # pragma: no cover 824 offset = fdt.GetFdtObj().totalsize() + data_offset 825 size = data_size 826 827 # This should never happen 828 else: # pragma: no cover 829 self.Raise(f'{path}: missing data properties') 830 831 section.SetOffsetSize(offset, size) 832 section.SetImagePos(self.image_pos) 833 834 def AddBintools(self, btools): 835 super().AddBintools(btools) 836 self.mkimage = self.AddBintool(btools, 'mkimage') 837 838 def CheckMissing(self, missing_list): 839 # We must use our private entry list for this since generator nodes 840 # which are removed from self._entries will otherwise not show up as 841 # missing 842 for entry in self._priv_entries.values(): 843 entry.CheckMissing(missing_list) 844 845 def CheckOptional(self, optional_list): 846 # We must use our private entry list for this since generator nodes 847 # which are removed from self._entries will otherwise not show up as 848 # optional 849 for entry in self._priv_entries.values(): 850 entry.CheckOptional(optional_list) 851 852 def CheckEntries(self): 853 pass 854 855 def UpdateSignatures(self, privatekey_fname, algo, input_fname): 856 uniq = self.GetUniqueName() 857 args = [ '-G', privatekey_fname, '-r', '-o', algo, '-F' ] 858 if input_fname: 859 fname = input_fname 860 else: 861 fname = tools.get_output_filename('%s.fit' % uniq) 862 tools.write_file(fname, self.GetData()) 863 args.append(fname) 864 865 if self.mkimage.run_cmd(*args) is None: 866 self.Raise("Missing tool: 'mkimage'") 867 868 data = tools.read_file(fname) 869 self.WriteData(data) 870