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