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