1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2018 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4 5"""Entry-type module for sections (groups of entries) 6 7Sections are entries which can contain other entries. This allows hierarchical 8images to be created. 9""" 10 11from collections import OrderedDict 12import concurrent.futures 13import re 14import sys 15 16from binman.entry import Entry 17from binman import state 18from dtoc import fdt_util 19from patman import tools 20from patman import tout 21from patman.tools import ToHexSize 22 23 24class Entry_section(Entry): 25 """Entry that contains other entries 26 27 Properties / Entry arguments: (see binman README for more information): 28 pad-byte: Pad byte to use when padding 29 sort-by-offset: True if entries should be sorted by offset, False if 30 they must be in-order in the device tree description 31 32 end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32) 33 34 skip-at-start: Number of bytes before the first entry starts. These 35 effectively adjust the starting offset of entries. For example, 36 if this is 16, then the first entry would start at 16. An entry 37 with offset = 20 would in fact be written at offset 4 in the image 38 file, since the first 16 bytes are skipped when writing. 39 name-prefix: Adds a prefix to the name of every entry in the section 40 when writing out the map 41 align_default: Default alignment for this section, if no alignment is 42 given in the entry 43 44 Properties: 45 allow_missing: True if this section permits external blobs to be 46 missing their contents. The second will produce an image but of 47 course it will not work. 48 49 Since a section is also an entry, it inherits all the properies of entries 50 too. 51 52 A section is an entry which can contain other entries, thus allowing 53 hierarchical images to be created. See 'Sections and hierarchical images' 54 in the binman README for more information. 55 """ 56 def __init__(self, section, etype, node, test=False): 57 if not test: 58 super().__init__(section, etype, node) 59 self._entries = OrderedDict() 60 self._pad_byte = 0 61 self._sort = False 62 self._skip_at_start = None 63 self._end_4gb = False 64 65 def ReadNode(self): 66 """Read properties from the section node""" 67 super().ReadNode() 68 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) 69 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset') 70 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') 71 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start') 72 if self._end_4gb: 73 if not self.size: 74 self.Raise("Section size must be provided when using end-at-4gb") 75 if self._skip_at_start is not None: 76 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'") 77 else: 78 self._skip_at_start = 0x100000000 - self.size 79 else: 80 if self._skip_at_start is None: 81 self._skip_at_start = 0 82 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix') 83 self.align_default = fdt_util.GetInt(self._node, 'align-default', 0) 84 filename = fdt_util.GetString(self._node, 'filename') 85 if filename: 86 self._filename = filename 87 88 self._ReadEntries() 89 90 def _ReadEntries(self): 91 for node in self._node.subnodes: 92 if node.name.startswith('hash') or node.name.startswith('signature'): 93 continue 94 entry = Entry.Create(self, node, 95 expanded=self.GetImage().use_expanded) 96 entry.ReadNode() 97 entry.SetPrefix(self._name_prefix) 98 self._entries[node.name] = entry 99 100 def _Raise(self, msg): 101 """Raises an error for this section 102 103 Args: 104 msg: Error message to use in the raise string 105 Raises: 106 ValueError() 107 """ 108 raise ValueError("Section '%s': %s" % (self._node.path, msg)) 109 110 def GetFdts(self): 111 fdts = {} 112 for entry in self._entries.values(): 113 fdts.update(entry.GetFdts()) 114 return fdts 115 116 def ProcessFdt(self, fdt): 117 """Allow entries to adjust the device tree 118 119 Some entries need to adjust the device tree for their purposes. This 120 may involve adding or deleting properties. 121 """ 122 todo = self._entries.values() 123 for passnum in range(3): 124 next_todo = [] 125 for entry in todo: 126 if not entry.ProcessFdt(fdt): 127 next_todo.append(entry) 128 todo = next_todo 129 if not todo: 130 break 131 if todo: 132 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' % 133 todo) 134 return True 135 136 def ExpandEntries(self): 137 super().ExpandEntries() 138 for entry in self._entries.values(): 139 entry.ExpandEntries() 140 141 def AddMissingProperties(self, have_image_pos): 142 """Add new properties to the device tree as needed for this entry""" 143 super().AddMissingProperties(have_image_pos) 144 if self.compress != 'none': 145 have_image_pos = False 146 for entry in self._entries.values(): 147 entry.AddMissingProperties(have_image_pos) 148 149 def ObtainContents(self): 150 return self.GetEntryContents() 151 152 def GetPaddedDataForEntry(self, entry, entry_data): 153 """Get the data for an entry including any padding 154 155 Gets the entry data and uses the section pad-byte value to add padding 156 before and after as defined by the pad-before and pad-after properties. 157 This does not consider alignment. 158 159 Args: 160 entry: Entry to check 161 162 Returns: 163 Contents of the entry along with any pad bytes before and 164 after it (bytes) 165 """ 166 pad_byte = (entry._pad_byte if isinstance(entry, Entry_section) 167 else self._pad_byte) 168 169 data = bytearray() 170 # Handle padding before the entry 171 if entry.pad_before: 172 data += tools.GetBytes(self._pad_byte, entry.pad_before) 173 174 # Add in the actual entry data 175 data += entry_data 176 177 # Handle padding after the entry 178 if entry.pad_after: 179 data += tools.GetBytes(self._pad_byte, entry.pad_after) 180 181 if entry.size: 182 data += tools.GetBytes(pad_byte, entry.size - len(data)) 183 184 self.Detail('GetPaddedDataForEntry: size %s' % ToHexSize(self.data)) 185 186 return data 187 188 def _BuildSectionData(self, required): 189 """Build the contents of a section 190 191 This places all entries at the right place, dealing with padding before 192 and after entries. It does not do padding for the section itself (the 193 pad-before and pad-after properties in the section items) since that is 194 handled by the parent section. 195 196 Args: 197 required: True if the data must be present, False if it is OK to 198 return None 199 200 Returns: 201 Contents of the section (bytes) 202 """ 203 section_data = bytearray() 204 205 for entry in self._entries.values(): 206 entry_data = entry.GetData(required) 207 if not required and entry_data is None: 208 return None 209 data = self.GetPaddedDataForEntry(entry, entry_data) 210 # Handle empty space before the entry 211 pad = (entry.offset or 0) - self._skip_at_start - len(section_data) 212 if pad > 0: 213 section_data += tools.GetBytes(self._pad_byte, pad) 214 215 # Add in the actual entry data 216 section_data += data 217 218 self.Detail('GetData: %d entries, total size %#x' % 219 (len(self._entries), len(section_data))) 220 return self.CompressData(section_data) 221 222 def GetPaddedData(self, data=None): 223 """Get the data for a section including any padding 224 225 Gets the section data and uses the parent section's pad-byte value to 226 add padding before and after as defined by the pad-before and pad-after 227 properties. If this is a top-level section (i.e. an image), this is the 228 same as GetData(), since padding is not supported. 229 230 This does not consider alignment. 231 232 Returns: 233 Contents of the section along with any pad bytes before and 234 after it (bytes) 235 """ 236 section = self.section or self 237 if data is None: 238 data = self.GetData() 239 return section.GetPaddedDataForEntry(self, data) 240 241 def GetData(self, required=True): 242 """Get the contents of an entry 243 244 This builds the contents of the section, stores this as the contents of 245 the section and returns it 246 247 Args: 248 required: True if the data must be present, False if it is OK to 249 return None 250 251 Returns: 252 bytes content of the section, made up for all all of its subentries. 253 This excludes any padding. If the section is compressed, the 254 compressed data is returned 255 """ 256 data = self._BuildSectionData(required) 257 if data is None: 258 return None 259 self.SetContents(data) 260 return data 261 262 def GetOffsets(self): 263 """Handle entries that want to set the offset/size of other entries 264 265 This calls each entry's GetOffsets() method. If it returns a list 266 of entries to update, it updates them. 267 """ 268 self.GetEntryOffsets() 269 return {} 270 271 def ResetForPack(self): 272 """Reset offset/size fields so that packing can be done again""" 273 super().ResetForPack() 274 for entry in self._entries.values(): 275 entry.ResetForPack() 276 277 def Pack(self, offset): 278 """Pack all entries into the section""" 279 self._PackEntries() 280 if self._sort: 281 self._SortEntries() 282 self._ExpandEntries() 283 284 data = self._BuildSectionData(True) 285 self.SetContents(data) 286 287 self.CheckSize() 288 289 offset = super().Pack(offset) 290 self.CheckEntries() 291 return offset 292 293 def _PackEntries(self): 294 """Pack all entries into the section""" 295 offset = self._skip_at_start 296 for entry in self._entries.values(): 297 offset = entry.Pack(offset) 298 return offset 299 300 def _ExpandEntries(self): 301 """Expand any entries that are permitted to""" 302 exp_entry = None 303 for entry in self._entries.values(): 304 if exp_entry: 305 exp_entry.ExpandToLimit(entry.offset) 306 exp_entry = None 307 if entry.expand_size: 308 exp_entry = entry 309 if exp_entry: 310 exp_entry.ExpandToLimit(self.size) 311 312 def _SortEntries(self): 313 """Sort entries by offset""" 314 entries = sorted(self._entries.values(), key=lambda entry: entry.offset) 315 self._entries.clear() 316 for entry in entries: 317 self._entries[entry._node.name] = entry 318 319 def CheckEntries(self): 320 """Check that entries do not overlap or extend outside the section""" 321 max_size = self.size if self.uncomp_size is None else self.uncomp_size 322 323 offset = 0 324 prev_name = 'None' 325 for entry in self._entries.values(): 326 entry.CheckEntries() 327 if (entry.offset < self._skip_at_start or 328 entry.offset + entry.size > self._skip_at_start + 329 max_size): 330 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the ' 331 "section '%s' starting at %#x (%d) " 332 'of size %#x (%d)' % 333 (entry.offset, entry.offset, entry.size, entry.size, 334 self._node.path, self._skip_at_start, 335 self._skip_at_start, max_size, max_size)) 336 if entry.offset < offset and entry.size: 337 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' " 338 "ending at %#x (%d)" % 339 (entry.offset, entry.offset, prev_name, offset, offset)) 340 offset = entry.offset + entry.size 341 prev_name = entry.GetPath() 342 343 def WriteSymbols(self, section): 344 """Write symbol values into binary files for access at run time""" 345 for entry in self._entries.values(): 346 entry.WriteSymbols(self) 347 348 def SetCalculatedProperties(self): 349 super().SetCalculatedProperties() 350 for entry in self._entries.values(): 351 entry.SetCalculatedProperties() 352 353 def SetImagePos(self, image_pos): 354 super().SetImagePos(image_pos) 355 if self.compress == 'none': 356 for entry in self._entries.values(): 357 entry.SetImagePos(image_pos + self.offset) 358 359 def ProcessContents(self): 360 sizes_ok_base = super(Entry_section, self).ProcessContents() 361 sizes_ok = True 362 for entry in self._entries.values(): 363 if not entry.ProcessContents(): 364 sizes_ok = False 365 return sizes_ok and sizes_ok_base 366 367 def WriteMap(self, fd, indent): 368 """Write a map of the section to a .map file 369 370 Args: 371 fd: File to write the map to 372 """ 373 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0, 374 self.size, self.image_pos) 375 for entry in self._entries.values(): 376 entry.WriteMap(fd, indent + 1) 377 378 def GetEntries(self): 379 return self._entries 380 381 def GetContentsByPhandle(self, phandle, source_entry, required): 382 """Get the data contents of an entry specified by a phandle 383 384 This uses a phandle to look up a node and and find the entry 385 associated with it. Then it returns the contents of that entry. 386 387 The node must be a direct subnode of this section. 388 389 Args: 390 phandle: Phandle to look up (integer) 391 source_entry: Entry containing that phandle (used for error 392 reporting) 393 required: True if the data must be present, False if it is OK to 394 return None 395 396 Returns: 397 data from associated entry (as a string), or None if not found 398 """ 399 node = self._node.GetFdt().LookupPhandle(phandle) 400 if not node: 401 source_entry.Raise("Cannot find node for phandle %d" % phandle) 402 for entry in self._entries.values(): 403 if entry._node == node: 404 return entry.GetData(required) 405 source_entry.Raise("Cannot find entry for node '%s'" % node.name) 406 407 def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None): 408 """Look up a symbol in an ELF file 409 410 Looks up a symbol in an ELF file. Only entry types which come from an 411 ELF image can be used by this function. 412 413 At present the only entry properties supported are: 414 offset 415 image_pos - 'base_addr' is added if this is not an end-at-4gb image 416 size 417 418 Args: 419 sym_name: Symbol name in the ELF file to look up in the format 420 _binman_<entry>_prop_<property> where <entry> is the name of 421 the entry and <property> is the property to find (e.g. 422 _binman_u_boot_prop_offset). As a special case, you can append 423 _any to <entry> to have it search for any matching entry. E.g. 424 _binman_u_boot_any_prop_offset will match entries called u-boot, 425 u-boot-img and u-boot-nodtb) 426 optional: True if the symbol is optional. If False this function 427 will raise if the symbol is not found 428 msg: Message to display if an error occurs 429 base_addr: Base address of image. This is added to the returned 430 image_pos in most cases so that the returned position indicates 431 where the targetted entry/binary has actually been loaded. But 432 if end-at-4gb is used, this is not done, since the binary is 433 already assumed to be linked to the ROM position and using 434 execute-in-place (XIP). 435 436 Returns: 437 Value that should be assigned to that symbol, or None if it was 438 optional and not found 439 440 Raises: 441 ValueError if the symbol is invalid or not found, or references a 442 property which is not supported 443 """ 444 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name) 445 if not m: 446 raise ValueError("%s: Symbol '%s' has invalid format" % 447 (msg, sym_name)) 448 entry_name, prop_name = m.groups() 449 entry_name = entry_name.replace('_', '-') 450 if not entries: 451 entries = self._entries 452 entry = entries.get(entry_name) 453 if not entry: 454 if entry_name.endswith('-any'): 455 root = entry_name[:-4] 456 for name in entries: 457 if name.startswith(root): 458 rest = name[len(root):] 459 if rest in ['', '-img', '-nodtb']: 460 entry = entries[name] 461 if not entry: 462 err = ("%s: Entry '%s' not found in list (%s)" % 463 (msg, entry_name, ','.join(entries.keys()))) 464 if optional: 465 print('Warning: %s' % err, file=sys.stderr) 466 return None 467 raise ValueError(err) 468 if prop_name == 'offset': 469 return entry.offset 470 elif prop_name == 'image_pos': 471 value = entry.image_pos 472 if not self.GetImage()._end_4gb: 473 value += base_addr 474 return value 475 if prop_name == 'size': 476 return entry.size 477 else: 478 raise ValueError("%s: No such property '%s'" % (msg, prop_name)) 479 480 def GetRootSkipAtStart(self): 481 """Get the skip-at-start value for the top-level section 482 483 This is used to find out the starting offset for root section that 484 contains this section. If this is a top-level section then it returns 485 the skip-at-start offset for this section. 486 487 This is used to get the absolute position of section within the image. 488 489 Returns: 490 Integer skip-at-start value for the root section containing this 491 section 492 """ 493 if self.section: 494 return self.section.GetRootSkipAtStart() 495 return self._skip_at_start 496 497 def GetStartOffset(self): 498 """Get the start offset for this section 499 500 Returns: 501 The first available offset in this section (typically 0) 502 """ 503 return self._skip_at_start 504 505 def GetImageSize(self): 506 """Get the size of the image containing this section 507 508 Returns: 509 Image size as an integer number of bytes, which may be None if the 510 image size is dynamic and its sections have not yet been packed 511 """ 512 return self.GetImage().size 513 514 def FindEntryType(self, etype): 515 """Find an entry type in the section 516 517 Args: 518 etype: Entry type to find 519 Returns: 520 entry matching that type, or None if not found 521 """ 522 for entry in self._entries.values(): 523 if entry.etype == etype: 524 return entry 525 return None 526 527 def GetEntryContents(self): 528 """Call ObtainContents() for each entry in the section 529 """ 530 def _CheckDone(entry): 531 if not entry.ObtainContents(): 532 next_todo.append(entry) 533 return entry 534 535 todo = self._entries.values() 536 for passnum in range(3): 537 threads = state.GetThreads() 538 next_todo = [] 539 540 if threads == 0: 541 for entry in todo: 542 _CheckDone(entry) 543 else: 544 with concurrent.futures.ThreadPoolExecutor( 545 max_workers=threads) as executor: 546 future_to_data = { 547 entry: executor.submit(_CheckDone, entry) 548 for entry in todo} 549 timeout = 60 550 if self.GetImage().test_section_timeout: 551 timeout = 0 552 done, not_done = concurrent.futures.wait( 553 future_to_data.values(), timeout=timeout) 554 # Make sure we check the result, so any exceptions are 555 # generated. Check the results in entry order, since tests 556 # may expect earlier entries to fail first. 557 for entry in todo: 558 job = future_to_data[entry] 559 job.result() 560 if not_done: 561 self.Raise('Timed out obtaining contents') 562 563 todo = next_todo 564 if not todo: 565 break 566 567 if todo: 568 self.Raise('Internal error: Could not complete processing of contents: remaining %s' % 569 todo) 570 return True 571 572 def _SetEntryOffsetSize(self, name, offset, size): 573 """Set the offset and size of an entry 574 575 Args: 576 name: Entry name to update 577 offset: New offset, or None to leave alone 578 size: New size, or None to leave alone 579 """ 580 entry = self._entries.get(name) 581 if not entry: 582 self._Raise("Unable to set offset/size for unknown entry '%s'" % 583 name) 584 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None 585 else None, size) 586 587 def GetEntryOffsets(self): 588 """Handle entries that want to set the offset/size of other entries 589 590 This calls each entry's GetOffsets() method. If it returns a list 591 of entries to update, it updates them. 592 """ 593 for entry in self._entries.values(): 594 offset_dict = entry.GetOffsets() 595 for name, info in offset_dict.items(): 596 self._SetEntryOffsetSize(name, *info) 597 598 def CheckSize(self): 599 contents_size = len(self.data) 600 601 size = self.size 602 if not size: 603 data = self.GetPaddedData(self.data) 604 size = len(data) 605 size = tools.Align(size, self.align_size) 606 607 if self.size and contents_size > self.size: 608 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" % 609 (contents_size, contents_size, self.size, self.size)) 610 if not self.size: 611 self.size = size 612 if self.size != tools.Align(self.size, self.align_size): 613 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" % 614 (self.size, self.size, self.align_size, 615 self.align_size)) 616 return size 617 618 def ListEntries(self, entries, indent): 619 """List the files in the section""" 620 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size, 621 self.image_pos, None, self.offset, self) 622 for entry in self._entries.values(): 623 entry.ListEntries(entries, indent + 1) 624 625 def LoadData(self, decomp=True): 626 for entry in self._entries.values(): 627 entry.LoadData(decomp) 628 self.Detail('Loaded data') 629 630 def GetImage(self): 631 """Get the image containing this section 632 633 Note that a top-level section is actually an Image, so this function may 634 return self. 635 636 Returns: 637 Image object containing this section 638 """ 639 if not self.section: 640 return self 641 return self.section.GetImage() 642 643 def GetSort(self): 644 """Check if the entries in this section will be sorted 645 646 Returns: 647 True if to be sorted, False if entries will be left in the order 648 they appear in the device tree 649 """ 650 return self._sort 651 652 def ReadData(self, decomp=True): 653 tout.Info("ReadData path='%s'" % self.GetPath()) 654 parent_data = self.section.ReadData(True) 655 offset = self.offset - self.section._skip_at_start 656 data = parent_data[offset:offset + self.size] 657 tout.Info( 658 '%s: Reading data from offset %#x-%#x (real %#x), size %#x, got %#x' % 659 (self.GetPath(), self.offset, self.offset + self.size, offset, 660 self.size, len(data))) 661 return data 662 663 def ReadChildData(self, child, decomp=True): 664 tout.Debug("ReadChildData for child '%s'" % child.GetPath()) 665 parent_data = self.ReadData(True) 666 offset = child.offset - self._skip_at_start 667 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" % 668 (child.GetPath(), child.offset, self._skip_at_start, offset)) 669 data = parent_data[offset:offset + child.size] 670 if decomp: 671 indata = data 672 data = tools.Decompress(indata, child.compress) 673 if child.uncomp_size: 674 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" % 675 (child.GetPath(), len(indata), child.compress, 676 len(data))) 677 return data 678 679 def WriteChildData(self, child): 680 return True 681 682 def SetAllowMissing(self, allow_missing): 683 """Set whether a section allows missing external blobs 684 685 Args: 686 allow_missing: True if allowed, False if not allowed 687 """ 688 self.allow_missing = allow_missing 689 for entry in self._entries.values(): 690 entry.SetAllowMissing(allow_missing) 691 692 def CheckMissing(self, missing_list): 693 """Check if any entries in this section have missing external blobs 694 695 If there are missing blobs, the entries are added to the list 696 697 Args: 698 missing_list: List of Entry objects to be added to 699 """ 700 for entry in self._entries.values(): 701 entry.CheckMissing(missing_list) 702 703 def _CollectEntries(self, entries, entries_by_name, add_entry): 704 """Collect all the entries in an section 705 706 This builds up a dict of entries in this section and all subsections. 707 Entries are indexed by path and by name. 708 709 Since all paths are unique, entries will not have any conflicts. However 710 entries_by_name make have conflicts if two entries have the same name 711 (e.g. with different parent sections). In this case, an entry at a 712 higher level in the hierarchy will win over a lower-level entry. 713 714 Args: 715 entries: dict to put entries: 716 key: entry path 717 value: Entry object 718 entries_by_name: dict to put entries 719 key: entry name 720 value: Entry object 721 add_entry: Entry to add 722 """ 723 entries[add_entry.GetPath()] = add_entry 724 to_add = add_entry.GetEntries() 725 if to_add: 726 for entry in to_add.values(): 727 entries[entry.GetPath()] = entry 728 for entry in to_add.values(): 729 self._CollectEntries(entries, entries_by_name, entry) 730 entries_by_name[add_entry.name] = add_entry 731 732 def MissingArgs(self, entry, missing): 733 """Report a missing argument, if enabled 734 735 For entries which require arguments, this reports an error if some are 736 missing. If missing entries are being ignored (e.g. because we read the 737 entry from an image rather than creating it), this function does 738 nothing. 739 740 Args: 741 missing: List of missing properties / entry args, each a string 742 """ 743 if not self._ignore_missing: 744 entry.Raise('Missing required properties/entry args: %s' % 745 (', '.join(missing))) 746