1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 """Entry-type module for sections (groups of entries)
7 Sections are entries which can contain other entries. This allows hierarchical
11 from collections import OrderedDict
12 import concurrent.futures
16 from binman.entry import Entry
17 from binman import state
18 from dtoc import fdt_util
19 from patman import tools
20 from patman import tout
21 from patman.tools import to_hex_size
24 class Entry_section(Entry):
25 """Entry that contains other entries
27 A section is an entry which can contain other entries, thus allowing
28 hierarchical images to be created. See 'Sections and hierarchical images'
29 in the binman README for more information.
31 The base implementation simply joins the various entries together, using
32 various rules about alignment, etc.
37 This class can be subclassed to support other file formats which hold
38 multiple entries, such as CBFS. To do this, override the following
39 functions. The documentation here describes what your function should do.
40 For example code, see etypes which subclass `Entry_section`, or `cbfs.py`
41 for a more involved example::
43 $ grep -l \(Entry_section tools/binman/etype/*.py
46 Call `super().ReadNode()`, then read any special properties for the
47 section. Then call `self.ReadEntries()` to read the entries.
49 Binman calls this at the start when reading the image description.
52 Read in the subnodes of the section. This may involve creating entries
53 of a particular etype automatically, as well as reading any special
54 properties in the entries. For each entry, entry.ReadNode() should be
55 called, to read the basic entry properties. The properties should be
56 added to `self._entries[]`, in the correct order, with a suitable name.
58 Binman calls this at the start when reading the image description.
60 BuildSectionData(required)
61 Create the custom file format that you want and return it as bytes.
62 This likely sets up a file header, then loops through the entries,
63 adding them to the file. For each entry, call `entry.GetData()` to
64 obtain the data. If that returns None, and `required` is False, then
65 this method must give up and return None. But if `required` is True then
66 it should assume that all data is valid.
68 Binman calls this when packing the image, to find out the size of
69 everything. It is called again at the end when building the final image.
71 SetImagePos(image_pos):
72 Call `super().SetImagePos(image_pos)`, then set the `image_pos` values
73 for each of the entries. This should use the custom file format to find
74 the `start offset` (and `image_pos`) of each entry. If the file format
75 uses compression in such a way that there is no offset available (other
76 than reading the whole file and decompressing it), then the offsets for
77 affected entries can remain unset (`None`). The size should also be set
80 Binman calls this after the image has been packed, to update the
81 location that all the entries ended up at.
83 ReadChildData(child, decomp, alt_format):
84 The default version of this may be good enough, if you are able to
85 implement SetImagePos() correctly. But that is a bit of a bypass, so
86 you can override this method to read from your custom file format. It
87 should read the entire entry containing the custom file using
88 `super().ReadData(True)`, then parse the file to get the data for the
89 given child, then return that data.
91 If your file format supports compression, the `decomp` argument tells
92 you whether to return the compressed data (`decomp` is False) or to
93 uncompress it first, then return the uncompressed data (`decomp` is
94 True). This is used by the `binman extract -U` option.
96 If your entry supports alternative formats, the alt_format provides the
97 alternative format that the user has selected. Your function should
98 return data in that format. This is used by the 'binman extract -l'
101 Binman calls this when reading in an image, in order to populate all the
102 entries with the data from that image (`binman ls`).
104 WriteChildData(child):
105 Binman calls this after `child.data` is updated, to inform the custom
106 file format about this, in case it needs to do updates.
108 The default version of this does nothing and probably needs to be
109 overridden for the 'binman replace' command to work. Your version should
110 use `child.data` to update the data for that child in the custom file
113 Binman calls this when updating an image that has been read in and in
114 particular to update the data for a particular entry (`binman replace`)
116 Properties / Entry arguments
117 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
119 See :ref:`develop/package/binman:Image description format` for more
123 Default alignment for this section, if no alignment is given in the
127 Pad byte to use when padding
130 True if entries should be sorted by offset, False if they must be
131 in-order in the device tree description
134 Used to build an x86 ROM which ends at 4GB (2^32)
137 Adds a prefix to the name of every entry in the section when writing out
141 Number of bytes before the first entry starts. These effectively adjust
142 the starting offset of entries. For example, if this is 16, then the
143 first entry would start at 16. An entry with offset = 20 would in fact
144 be written at offset 4 in the image file, since the first 16 bytes are
145 skipped when writing.
147 Since a section is also an entry, it inherits all the properies of entries
150 Note that the `allow_missing` member controls whether this section permits
151 external blobs to be missing their contents. The option will produce an
152 image but of course it will not work. It is useful to make sure that
153 Continuous Integration systems can build without the binaries being
154 available. This is set by the `SetAllowMissing()` method, if
155 `--allow-missing` is passed to binman.
157 def __init__(self, section, etype, node, test=False):
159 super().__init__(section, etype, node)
160 self._entries = OrderedDict()
163 self._skip_at_start = None
164 self._end_4gb = False
165 self._ignore_missing = False
168 """Read properties from the section node"""
170 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
171 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
172 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
173 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
176 self.Raise("Section size must be provided when using end-at-4gb")
177 if self._skip_at_start is not None:
178 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
180 self._skip_at_start = 0x100000000 - self.size
182 if self._skip_at_start is None:
183 self._skip_at_start = 0
184 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
185 self.align_default = fdt_util.GetInt(self._node, 'align-default', 0)
189 def ReadEntries(self):
190 for node in self._node.subnodes:
191 if node.name.startswith('hash') or node.name.startswith('signature'):
193 entry = Entry.Create(self, node,
194 expanded=self.GetImage().use_expanded,
195 missing_etype=self.GetImage().missing_etype)
197 entry.SetPrefix(self._name_prefix)
198 self._entries[node.name] = entry
200 def _Raise(self, msg):
201 """Raises an error for this section
204 msg (str): Error message to use in the raise string
208 raise ValueError("Section '%s': %s" % (self._node.path, msg))
212 for entry in self._entries.values():
213 fdts.update(entry.GetFdts())
216 def ProcessFdt(self, fdt):
217 """Allow entries to adjust the device tree
219 Some entries need to adjust the device tree for their purposes. This
220 may involve adding or deleting properties.
222 todo = self._entries.values()
223 for passnum in range(3):
226 if not entry.ProcessFdt(fdt):
227 next_todo.append(entry)
232 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
236 def gen_entries(self):
237 super().gen_entries()
238 for entry in self._entries.values():
241 def AddMissingProperties(self, have_image_pos):
242 """Add new properties to the device tree as needed for this entry"""
243 super().AddMissingProperties(have_image_pos)
244 if self.compress != 'none':
245 have_image_pos = False
246 for entry in self._entries.values():
247 entry.AddMissingProperties(have_image_pos)
249 def ObtainContents(self, fake_size=0, skip_entry=None):
250 return self.GetEntryContents(skip_entry=skip_entry)
252 def GetPaddedDataForEntry(self, entry, entry_data):
253 """Get the data for an entry including any padding
255 Gets the entry data and uses the section pad-byte value to add padding
256 before and after as defined by the pad-before and pad-after properties.
257 This does not consider alignment.
260 entry: Entry to check
263 Contents of the entry along with any pad bytes before and
266 pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
270 # Handle padding before the entry
272 data += tools.get_bytes(self._pad_byte, entry.pad_before)
274 # Add in the actual entry data
277 # Handle padding after the entry
279 data += tools.get_bytes(self._pad_byte, entry.pad_after)
282 data += tools.get_bytes(pad_byte, entry.size - len(data))
284 self.Detail('GetPaddedDataForEntry: size %s' % to_hex_size(self.data))
288 def BuildSectionData(self, required):
289 """Build the contents of a section
291 This places all entries at the right place, dealing with padding before
292 and after entries. It does not do padding for the section itself (the
293 pad-before and pad-after properties in the section items) since that is
294 handled by the parent section.
296 This should be overridden by subclasses which want to build their own
297 data structure for the section.
300 required: True if the data must be present, False if it is OK to
304 Contents of the section (bytes)
306 section_data = bytearray()
308 for entry in self._entries.values():
309 entry_data = entry.GetData(required)
311 # This can happen when this section is referenced from a collection
312 # earlier in the image description. See testCollectionSection().
313 if not required and entry_data is None:
315 data = self.GetPaddedDataForEntry(entry, entry_data)
316 # Handle empty space before the entry
317 pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
319 section_data += tools.get_bytes(self._pad_byte, pad)
321 # Add in the actual entry data
324 self.Detail('GetData: %d entries, total size %#x' %
325 (len(self._entries), len(section_data)))
326 return self.CompressData(section_data)
328 def GetPaddedData(self, data=None):
329 """Get the data for a section including any padding
331 Gets the section data and uses the parent section's pad-byte value to
332 add padding before and after as defined by the pad-before and pad-after
333 properties. If this is a top-level section (i.e. an image), this is the
334 same as GetData(), since padding is not supported.
336 This does not consider alignment.
339 Contents of the section along with any pad bytes before and
342 section = self.section or self
344 data = self.GetData()
345 return section.GetPaddedDataForEntry(self, data)
347 def GetData(self, required=True):
348 """Get the contents of an entry
350 This builds the contents of the section, stores this as the contents of
351 the section and returns it
354 required: True if the data must be present, False if it is OK to
358 bytes content of the section, made up for all all of its subentries.
359 This excludes any padding. If the section is compressed, the
360 compressed data is returned
362 data = self.BuildSectionData(required)
365 self.SetContents(data)
368 def GetOffsets(self):
369 """Handle entries that want to set the offset/size of other entries
371 This calls each entry's GetOffsets() method. If it returns a list
372 of entries to update, it updates them.
374 self.GetEntryOffsets()
377 def ResetForPack(self):
378 """Reset offset/size fields so that packing can be done again"""
379 super().ResetForPack()
380 for entry in self._entries.values():
383 def Pack(self, offset):
384 """Pack all entries into the section"""
388 self._extend_entries()
390 data = self.BuildSectionData(True)
391 self.SetContents(data)
395 offset = super().Pack(offset)
399 def _PackEntries(self):
400 """Pack all entries into the section"""
401 offset = self._skip_at_start
402 for entry in self._entries.values():
403 offset = entry.Pack(offset)
406 def _extend_entries(self):
407 """Extend any entries that are permitted to"""
409 for entry in self._entries.values():
411 exp_entry.extend_to_limit(entry.offset)
413 if entry.extend_size:
416 exp_entry.extend_to_limit(self.size)
418 def _SortEntries(self):
419 """Sort entries by offset"""
420 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
421 self._entries.clear()
422 for entry in entries:
423 self._entries[entry._node.name] = entry
425 def CheckEntries(self):
426 """Check that entries do not overlap or extend outside the section"""
427 max_size = self.size if self.uncomp_size is None else self.uncomp_size
431 for entry in self._entries.values():
433 if (entry.offset < self._skip_at_start or
434 entry.offset + entry.size > self._skip_at_start +
436 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
437 "section '%s' starting at %#x (%d) "
439 (entry.offset, entry.offset, entry.size, entry.size,
440 self._node.path, self._skip_at_start,
441 self._skip_at_start, max_size, max_size))
442 if entry.offset < offset and entry.size:
443 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
444 "ending at %#x (%d)" %
445 (entry.offset, entry.offset, prev_name, offset, offset))
446 offset = entry.offset + entry.size
447 prev_name = entry.GetPath()
449 def WriteSymbols(self, section):
450 """Write symbol values into binary files for access at run time"""
451 for entry in self._entries.values():
452 entry.WriteSymbols(self)
454 def SetCalculatedProperties(self):
455 super().SetCalculatedProperties()
456 for entry in self._entries.values():
457 entry.SetCalculatedProperties()
459 def SetImagePos(self, image_pos):
460 super().SetImagePos(image_pos)
461 if self.compress == 'none':
462 for entry in self._entries.values():
463 entry.SetImagePos(image_pos + self.offset)
465 def ProcessContents(self):
466 sizes_ok_base = super(Entry_section, self).ProcessContents()
468 for entry in self._entries.values():
469 if not entry.ProcessContents():
471 return sizes_ok and sizes_ok_base
473 def WriteMap(self, fd, indent):
474 """Write a map of the section to a .map file
477 fd: File to write the map to
479 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
480 self.size, self.image_pos)
481 for entry in self._entries.values():
482 entry.WriteMap(fd, indent + 1)
484 def GetEntries(self):
487 def GetContentsByPhandle(self, phandle, source_entry, required):
488 """Get the data contents of an entry specified by a phandle
490 This uses a phandle to look up a node and and find the entry
491 associated with it. Then it returns the contents of that entry.
493 The node must be a direct subnode of this section.
496 phandle: Phandle to look up (integer)
497 source_entry: Entry containing that phandle (used for error
499 required: True if the data must be present, False if it is OK to
503 data from associated entry (as a string), or None if not found
505 node = self._node.GetFdt().LookupPhandle(phandle)
507 source_entry.Raise("Cannot find node for phandle %d" % phandle)
508 entry = self.FindEntryByNode(node)
510 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
511 return entry.GetData(required)
513 def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None):
514 """Look up a symbol in an ELF file
516 Looks up a symbol in an ELF file. Only entry types which come from an
517 ELF image can be used by this function.
519 At present the only entry properties supported are:
521 image_pos - 'base_addr' is added if this is not an end-at-4gb image
525 sym_name: Symbol name in the ELF file to look up in the format
526 _binman_<entry>_prop_<property> where <entry> is the name of
527 the entry and <property> is the property to find (e.g.
528 _binman_u_boot_prop_offset). As a special case, you can append
529 _any to <entry> to have it search for any matching entry. E.g.
530 _binman_u_boot_any_prop_offset will match entries called u-boot,
531 u-boot-img and u-boot-nodtb)
532 optional: True if the symbol is optional. If False this function
533 will raise if the symbol is not found
534 msg: Message to display if an error occurs
535 base_addr: Base address of image. This is added to the returned
536 image_pos in most cases so that the returned position indicates
537 where the targetted entry/binary has actually been loaded. But
538 if end-at-4gb is used, this is not done, since the binary is
539 already assumed to be linked to the ROM position and using
540 execute-in-place (XIP).
543 Value that should be assigned to that symbol, or None if it was
544 optional and not found
547 ValueError if the symbol is invalid or not found, or references a
548 property which is not supported
550 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
552 raise ValueError("%s: Symbol '%s' has invalid format" %
554 entry_name, prop_name = m.groups()
555 entry_name = entry_name.replace('_', '-')
557 entries = self._entries
558 entry = entries.get(entry_name)
560 if entry_name.endswith('-any'):
561 root = entry_name[:-4]
563 if name.startswith(root):
564 rest = name[len(root):]
565 if rest in ['', '-img', '-nodtb']:
566 entry = entries[name]
568 err = ("%s: Entry '%s' not found in list (%s)" %
569 (msg, entry_name, ','.join(entries.keys())))
571 print('Warning: %s' % err, file=sys.stderr)
573 raise ValueError(err)
574 if prop_name == 'offset':
576 elif prop_name == 'image_pos':
577 value = entry.image_pos
578 if not self.GetImage()._end_4gb:
581 if prop_name == 'size':
584 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
586 def GetRootSkipAtStart(self):
587 """Get the skip-at-start value for the top-level section
589 This is used to find out the starting offset for root section that
590 contains this section. If this is a top-level section then it returns
591 the skip-at-start offset for this section.
593 This is used to get the absolute position of section within the image.
596 Integer skip-at-start value for the root section containing this
600 return self.section.GetRootSkipAtStart()
601 return self._skip_at_start
603 def GetStartOffset(self):
604 """Get the start offset for this section
607 The first available offset in this section (typically 0)
609 return self._skip_at_start
611 def GetImageSize(self):
612 """Get the size of the image containing this section
615 Image size as an integer number of bytes, which may be None if the
616 image size is dynamic and its sections have not yet been packed
618 return self.GetImage().size
620 def FindEntryType(self, etype):
621 """Find an entry type in the section
624 etype: Entry type to find
626 entry matching that type, or None if not found
628 for entry in self._entries.values():
629 if entry.etype == etype:
633 def GetEntryContents(self, skip_entry=None):
634 """Call ObtainContents() for each entry in the section
636 def _CheckDone(entry):
637 if entry != skip_entry:
638 if not entry.ObtainContents():
639 next_todo.append(entry)
642 todo = self._entries.values()
643 for passnum in range(3):
644 threads = state.GetThreads()
651 with concurrent.futures.ThreadPoolExecutor(
652 max_workers=threads) as executor:
654 entry: executor.submit(_CheckDone, entry)
657 if self.GetImage().test_section_timeout:
659 done, not_done = concurrent.futures.wait(
660 future_to_data.values(), timeout=timeout)
661 # Make sure we check the result, so any exceptions are
662 # generated. Check the results in entry order, since tests
663 # may expect earlier entries to fail first.
665 job = future_to_data[entry]
668 self.Raise('Timed out obtaining contents')
675 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
679 def _SetEntryOffsetSize(self, name, offset, size):
680 """Set the offset and size of an entry
683 name: Entry name to update
684 offset: New offset, or None to leave alone
685 size: New size, or None to leave alone
687 entry = self._entries.get(name)
689 self._Raise("Unable to set offset/size for unknown entry '%s'" %
691 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
694 def GetEntryOffsets(self):
695 """Handle entries that want to set the offset/size of other entries
697 This calls each entry's GetOffsets() method. If it returns a list
698 of entries to update, it updates them.
700 for entry in self._entries.values():
701 offset_dict = entry.GetOffsets()
702 for name, info in offset_dict.items():
703 self._SetEntryOffsetSize(name, *info)
706 contents_size = len(self.data)
710 data = self.GetPaddedData(self.data)
712 size = tools.align(size, self.align_size)
714 if self.size and contents_size > self.size:
715 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
716 (contents_size, contents_size, self.size, self.size))
719 if self.size != tools.align(self.size, self.align_size):
720 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
721 (self.size, self.size, self.align_size,
725 def ListEntries(self, entries, indent):
726 """List the files in the section"""
727 Entry.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
728 self.image_pos, None, self.offset, self)
729 for entry in self._entries.values():
730 entry.ListEntries(entries, indent + 1)
732 def LoadData(self, decomp=True):
733 for entry in self._entries.values():
734 entry.LoadData(decomp)
735 self.Detail('Loaded data')
738 """Get the image containing this section
740 Note that a top-level section is actually an Image, so this function may
744 Image object containing this section
748 return self.section.GetImage()
751 """Check if the entries in this section will be sorted
754 True if to be sorted, False if entries will be left in the order
755 they appear in the device tree
759 def ReadData(self, decomp=True, alt_format=None):
760 tout.info("ReadData path='%s'" % self.GetPath())
761 parent_data = self.section.ReadData(True, alt_format)
762 offset = self.offset - self.section._skip_at_start
763 data = parent_data[offset:offset + self.size]
765 '%s: Reading data from offset %#x-%#x (real %#x), size %#x, got %#x' %
766 (self.GetPath(), self.offset, self.offset + self.size, offset,
767 self.size, len(data)))
770 def ReadChildData(self, child, decomp=True, alt_format=None):
771 tout.debug(f"ReadChildData for child '{child.GetPath()}'")
772 parent_data = self.ReadData(True, alt_format)
773 offset = child.offset - self._skip_at_start
774 tout.debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
775 (child.GetPath(), child.offset, self._skip_at_start, offset))
776 data = parent_data[offset:offset + child.size]
779 data = child.DecompressData(indata)
780 if child.uncomp_size:
781 tout.info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
782 (child.GetPath(), len(indata), child.compress,
785 new_data = child.GetAltFormat(data, alt_format)
786 if new_data is not None:
790 def WriteData(self, data, decomp=True):
791 self.Raise("Replacing sections is not implemented yet")
793 def WriteChildData(self, child):
796 def SetAllowMissing(self, allow_missing):
797 """Set whether a section allows missing external blobs
800 allow_missing: True if allowed, False if not allowed
802 self.allow_missing = allow_missing
803 for entry in self._entries.values():
804 entry.SetAllowMissing(allow_missing)
806 def SetAllowFakeBlob(self, allow_fake):
807 """Set whether a section allows to create a fake blob
810 allow_fake_blob: True if allowed, False if not allowed
812 super().SetAllowFakeBlob(allow_fake)
813 for entry in self._entries.values():
814 entry.SetAllowFakeBlob(allow_fake)
816 def CheckMissing(self, missing_list):
817 """Check if any entries in this section have missing external blobs
819 If there are missing blobs, the entries are added to the list
822 missing_list: List of Entry objects to be added to
824 for entry in self._entries.values():
825 entry.CheckMissing(missing_list)
827 def CheckFakedBlobs(self, faked_blobs_list):
828 """Check if any entries in this section have faked external blobs
830 If there are faked blobs, the entries are added to the list
833 fake_blobs_list: List of Entry objects to be added to
835 for entry in self._entries.values():
836 entry.CheckFakedBlobs(faked_blobs_list)
838 def check_missing_bintools(self, missing_list):
839 """Check if any entries in this section have missing bintools
841 If there are missing bintools, these are added to the list
844 missing_list: List of Bintool objects to be added to
846 super().check_missing_bintools(missing_list)
847 for entry in self._entries.values():
848 entry.check_missing_bintools(missing_list)
850 def _CollectEntries(self, entries, entries_by_name, add_entry):
851 """Collect all the entries in an section
853 This builds up a dict of entries in this section and all subsections.
854 Entries are indexed by path and by name.
856 Since all paths are unique, entries will not have any conflicts. However
857 entries_by_name make have conflicts if two entries have the same name
858 (e.g. with different parent sections). In this case, an entry at a
859 higher level in the hierarchy will win over a lower-level entry.
862 entries: dict to put entries:
865 entries_by_name: dict to put entries
868 add_entry: Entry to add
870 entries[add_entry.GetPath()] = add_entry
871 to_add = add_entry.GetEntries()
873 for entry in to_add.values():
874 entries[entry.GetPath()] = entry
875 for entry in to_add.values():
876 self._CollectEntries(entries, entries_by_name, entry)
877 entries_by_name[add_entry.name] = add_entry
879 def MissingArgs(self, entry, missing):
880 """Report a missing argument, if enabled
882 For entries which require arguments, this reports an error if some are
883 missing. If missing entries are being ignored (e.g. because we read the
884 entry from an image rather than creating it), this function does
888 entry (Entry): Entry to raise the error on
889 missing (list of str): List of missing properties / entry args, each
892 if not self._ignore_missing:
893 missing = ', '.join(missing)
894 entry.Raise(f'Missing required properties/entry args: {missing}')
896 def CheckAltFormats(self, alt_formats):
897 for entry in self._entries.values():
898 entry.CheckAltFormats(alt_formats)
900 def AddBintools(self, btools):
901 super().AddBintools(btools)
902 for entry in self._entries.values():
903 entry.AddBintools(btools)