1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
4 # Base class for all entries
7 from collections import namedtuple
12 from dtoc import fdt_util
13 from patman import tools
14 from patman.tools import ToHex, ToHexSize
15 from patman import tout
20 # An argument which can be passed to entries on the command line, in lieu of
21 # device-tree properties.
22 EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
24 # Information about an entry for use when displaying summaries
25 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
26 'image_pos', 'uncomp_size', 'offset',
30 """An Entry in the section
32 An entry corresponds to a single node in the device-tree description
33 of the section. Each entry ends up being a part of the final section.
34 Entries can be placed either right next to each other, or with padding
35 between them. The type of the entry determines the data that is in it.
37 This class is not used by itself. All entry objects are subclasses of
41 section: Section object containing this entry
42 node: The node that created this entry
43 offset: Offset of entry within the section, None if not known yet (in
44 which case it will be calculated by Pack())
45 size: Entry size in bytes, None if not known
46 pre_reset_size: size as it was before ResetForPack(). This allows us to
47 keep track of the size we started with and detect size changes
48 uncomp_size: Size of uncompressed data in bytes, if the entry is
50 contents_size: Size of contents in bytes, 0 by default
51 align: Entry start offset alignment relative to the start of the
52 containing section, or None
53 align_size: Entry size alignment, or None
54 align_end: Entry end offset alignment relative to the start of the
55 containing section, or None
56 pad_before: Number of pad bytes before the contents when it is placed
57 in the containing section, 0 if none. The pad bytes become part of
59 pad_after: Number of pad bytes after the contents when it is placed in
60 the containing section, 0 if none. The pad bytes become part of
62 data: Contents of entry (string of bytes). This does not include
63 padding created by pad_before or pad_after. If the entry is
64 compressed, this contains the compressed data.
65 uncomp_data: Original uncompressed data, if this entry is compressed,
67 compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
68 orig_offset: Original offset value read from node
69 orig_size: Original size value read from node
70 missing: True if this entry is missing its contents
71 allow_missing: Allow children of this entry to be missing (used by
72 subclasses such as Entry_section)
73 external: True if this entry contains an external binary blob
75 def __init__(self, section, etype, node, name_prefix=''):
76 # Put this here to allow entry-docs and help to work without libfdt
78 from binman import state
80 self.section = section
83 self.name = node and (name_prefix + node.name) or 'none'
86 self.pre_reset_size = None
87 self.uncomp_size = None
89 self.uncomp_data = None
90 self.contents_size = 0
92 self.align_size = None
96 self.offset_unset = False
98 self._expand_size = False
99 self.compress = 'none'
101 self.external = False
102 self.allow_missing = False
105 def Lookup(node_path, etype, expanded):
106 """Look up the entry class for a node.
109 node_node: Path name of Node object containing information about
110 the entry to create (used for errors)
111 etype: Entry type to use
112 expanded: Use the expanded version of etype
115 The entry class object if found, else None if not found and expanded
119 ValueError if expanded is False and the class is not found
121 # Convert something like 'u-boot@0' to 'u_boot' since we are only
122 # interested in the type.
123 module_name = etype.replace('-', '_')
125 if '@' in module_name:
126 module_name = module_name.split('@')[0]
128 module_name += '_expanded'
129 module = modules.get(module_name)
131 # Also allow entry-type modules to be brought in from the etype directory.
133 # Import the module if we have not already done so.
136 module = importlib.import_module('binman.etype.' + module_name)
137 except ImportError as e:
140 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
141 (etype, node_path, module_name, e))
142 modules[module_name] = module
144 # Look up the expected class name
145 return getattr(module, 'Entry_%s' % module_name)
148 def Create(section, node, etype=None, expanded=False):
149 """Create a new entry for a node.
152 section: Section object containing this node
153 node: Node object containing information about the entry to
155 etype: Entry type to use, or None to work it out (used for tests)
156 expanded: True to use expanded versions of entries, where available
159 A new Entry object of the correct type (a subclass of Entry)
162 etype = fdt_util.GetString(node, 'type', node.name)
163 obj = Entry.Lookup(node.path, etype, expanded)
165 # Check whether to use the expanded entry
166 new_etype = etype + '-expanded'
167 if obj.UseExpanded(node, etype, new_etype):
172 obj = Entry.Lookup(node.path, etype, False)
174 # Call its constructor to get the object we want.
175 return obj(section, etype, node)
178 """Read entry information from the node
180 This must be called as the first thing after the Entry is created.
182 This reads all the fields we recognise from the node, ready for use.
184 if 'pos' in self._node.props:
185 self.Raise("Please use 'offset' instead of 'pos'")
186 self.offset = fdt_util.GetInt(self._node, 'offset')
187 self.size = fdt_util.GetInt(self._node, 'size')
188 self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
189 self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
190 if self.GetImage().copy_to_orig:
191 self.orig_offset = self.offset
192 self.orig_size = self.size
194 # These should not be set in input files, but are set in an FDT map,
195 # which is also read by this code.
196 self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
197 self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
199 self.align = fdt_util.GetInt(self._node, 'align')
200 if tools.NotPowerOfTwo(self.align):
201 raise ValueError("Node '%s': Alignment %s must be a power of two" %
202 (self._node.path, self.align))
203 self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
204 self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
205 self.align_size = fdt_util.GetInt(self._node, 'align-size')
206 if tools.NotPowerOfTwo(self.align_size):
207 self.Raise("Alignment size %s must be a power of two" %
209 self.align_end = fdt_util.GetInt(self._node, 'align-end')
210 self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
211 self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
212 self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
214 # This is only supported by blobs and sections at present
215 self.compress = fdt_util.GetString(self._node, 'compress', 'none')
217 def GetDefaultFilename(self):
221 """Get the device trees used by this entry
224 Empty dict, if this entry is not a .dtb, otherwise:
226 key: Filename from this entry (without the path)
228 Entry object for this dtb
229 Filename of file containing this dtb
233 def ExpandEntries(self):
234 """Expand out entries which produce other entries
236 Some entries generate subnodes automatically, from which sub-entries
237 are then created. This method allows those to be added to the binman
238 definition for the current image. An entry which implements this method
239 should call state.AddSubnode() to add a subnode and can add properties
240 with state.AddString(), etc.
242 An example is 'files', which produces a section containing a list of
247 def AddMissingProperties(self, have_image_pos):
248 """Add new properties to the device tree as needed for this entry
251 have_image_pos: True if this entry has an image position. This can
252 be False if its parent section is compressed, since compression
253 groups all entries together into a compressed block of data,
254 obscuring the start of each individual child entry
256 for prop in ['offset', 'size']:
257 if not prop in self._node.props:
258 state.AddZeroProp(self._node, prop)
259 if have_image_pos and 'image-pos' not in self._node.props:
260 state.AddZeroProp(self._node, 'image-pos')
261 if self.GetImage().allow_repack:
262 if self.orig_offset is not None:
263 state.AddZeroProp(self._node, 'orig-offset', True)
264 if self.orig_size is not None:
265 state.AddZeroProp(self._node, 'orig-size', True)
267 if self.compress != 'none':
268 state.AddZeroProp(self._node, 'uncomp-size')
269 err = state.CheckAddHashProp(self._node)
273 def SetCalculatedProperties(self):
274 """Set the value of device-tree properties calculated by binman"""
275 state.SetInt(self._node, 'offset', self.offset)
276 state.SetInt(self._node, 'size', self.size)
277 base = self.section.GetRootSkipAtStart() if self.section else 0
278 if self.image_pos is not None:
279 state.SetInt(self._node, 'image-pos', self.image_pos - base)
280 if self.GetImage().allow_repack:
281 if self.orig_offset is not None:
282 state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
283 if self.orig_size is not None:
284 state.SetInt(self._node, 'orig-size', self.orig_size, True)
285 if self.uncomp_size is not None:
286 state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
287 state.CheckSetHashValue(self._node, self.GetData)
289 def ProcessFdt(self, fdt):
290 """Allow entries to adjust the device tree
292 Some entries need to adjust the device tree for their purposes. This
293 may involve adding or deleting properties.
296 True if processing is complete
297 False if processing could not be completed due to a dependency.
298 This will cause the entry to be retried after others have been
303 def SetPrefix(self, prefix):
304 """Set the name prefix for a node
307 prefix: Prefix to set, or '' to not use a prefix
310 self.name = prefix + self.name
312 def SetContents(self, data):
313 """Set the contents of an entry
315 This sets both the data and content_size properties
318 data: Data to set to the contents (bytes)
321 self.contents_size = len(self.data)
323 def ProcessContentsUpdate(self, data):
324 """Update the contents of an entry, after the size is fixed
326 This checks that the new data is the same size as the old. If the size
327 has changed, this triggers a re-run of the packing algorithm.
330 data: Data to set to the contents (bytes)
333 ValueError if the new data size is not the same as the old
337 if state.AllowEntryExpansion() and new_size > self.contents_size:
338 # self.data will indicate the new size needed
340 elif state.AllowEntryContraction() and new_size < self.contents_size:
343 # If not allowed to change, try to deal with it or give up
345 if new_size > self.contents_size:
346 self.Raise('Cannot update entry size from %d to %d' %
347 (self.contents_size, new_size))
349 # Don't let the data shrink. Pad it if necessary
350 if size_ok and new_size < self.contents_size:
351 data += tools.GetBytes(0, self.contents_size - new_size)
354 tout.Debug("Entry '%s' size change from %s to %s" % (
355 self._node.path, ToHex(self.contents_size),
357 self.SetContents(data)
360 def ObtainContents(self):
361 """Figure out the contents of an entry.
364 True if the contents were found, False if another call is needed
365 after the other entries are processed.
367 # No contents by default: subclasses can implement this
370 def ResetForPack(self):
371 """Reset offset/size fields so that packing can be done again"""
372 self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
373 (ToHex(self.offset), ToHex(self.orig_offset),
374 ToHex(self.size), ToHex(self.orig_size)))
375 self.pre_reset_size = self.size
376 self.offset = self.orig_offset
377 self.size = self.orig_size
379 def Pack(self, offset):
380 """Figure out how to pack the entry into the section
382 Most of the time the entries are not fully specified. There may be
383 an alignment but no size. In that case we take the size from the
384 contents of the entry.
386 If an entry has no hard-coded offset, it will be placed at @offset.
388 Once this function is complete, both the offset and size of the
392 Current section offset pointer
395 New section offset pointer (after this entry)
397 self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
398 (ToHex(self.offset), ToHex(self.size),
400 if self.offset is None:
401 if self.offset_unset:
402 self.Raise('No offset set with offset-unset: should another '
403 'entry provide this correct offset?')
404 self.offset = tools.Align(offset, self.align)
405 needed = self.pad_before + self.contents_size + self.pad_after
406 needed = tools.Align(needed, self.align_size)
410 new_offset = self.offset + size
411 aligned_offset = tools.Align(new_offset, self.align_end)
412 if aligned_offset != new_offset:
413 size = aligned_offset - self.offset
414 new_offset = aligned_offset
419 if self.size < needed:
420 self.Raise("Entry contents size is %#x (%d) but entry size is "
421 "%#x (%d)" % (needed, needed, self.size, self.size))
422 # Check that the alignment is correct. It could be wrong if the
423 # and offset or size values were provided (i.e. not calculated), but
424 # conflict with the provided alignment values
425 if self.size != tools.Align(self.size, self.align_size):
426 self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
427 (self.size, self.size, self.align_size, self.align_size))
428 if self.offset != tools.Align(self.offset, self.align):
429 self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
430 (self.offset, self.offset, self.align, self.align))
431 self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
432 (self.offset, self.size, self.contents_size, new_offset))
436 def Raise(self, msg):
437 """Convenience function to raise an error referencing a node"""
438 raise ValueError("Node '%s': %s" % (self._node.path, msg))
440 def Detail(self, msg):
441 """Convenience function to log detail referencing a node"""
442 tag = "Node '%s'" % self._node.path
443 tout.Detail('%30s: %s' % (tag, msg))
445 def GetEntryArgsOrProps(self, props, required=False):
446 """Return the values of a set of properties
449 props: List of EntryArg objects
452 ValueError if a property is not found
457 python_prop = prop.name.replace('-', '_')
458 if hasattr(self, python_prop):
459 value = getattr(self, python_prop)
463 value = self.GetArg(prop.name, prop.datatype)
464 if value is None and required:
465 missing.append(prop.name)
468 self.GetImage().MissingArgs(self, missing)
472 """Get the path of a node
475 Full path of the node for this entry
477 return self._node.path
480 """Get the contents of an entry
483 bytes content of the entry, excluding any padding. If the entry is
484 compressed, the compressed data is returned
486 self.Detail('GetData: size %s' % ToHexSize(self.data))
489 def GetPaddedData(self, data=None):
490 """Get the data for an entry including any padding
492 Gets the entry data and uses its section's pad-byte value to add padding
493 before and after as defined by the pad-before and pad-after properties.
495 This does not consider alignment.
498 Contents of the entry along with any pad bytes before and
502 data = self.GetData()
503 return self.section.GetPaddedDataForEntry(self, data)
505 def GetOffsets(self):
506 """Get the offsets for siblings
508 Some entry types can contain information about the position or size of
509 other entries. An example of this is the Intel Flash Descriptor, which
510 knows where the Intel Management Engine section should go.
512 If this entry knows about the position of other entries, it can specify
513 this by returning values here
518 value: List containing position and size of the given entry
519 type. Either can be None if not known
523 def SetOffsetSize(self, offset, size):
524 """Set the offset and/or size of an entry
527 offset: New offset, or None to leave alone
528 size: New size, or None to leave alone
530 if offset is not None:
535 def SetImagePos(self, image_pos):
536 """Set the position in the image
539 image_pos: Position of this entry in the image
541 self.image_pos = image_pos + self.offset
543 def ProcessContents(self):
544 """Do any post-packing updates of entry contents
546 This function should call ProcessContentsUpdate() to update the entry
547 contents, if necessary, returning its return value here.
550 data: Data to set to the contents (bytes)
553 True if the new data size is OK, False if expansion is needed
556 ValueError if the new data size is not the same as the old and
557 state.AllowEntryExpansion() is False
561 def WriteSymbols(self, section):
562 """Write symbol values into binary files for access at run time
565 section: Section containing the entry
569 def CheckEntries(self):
570 """Check that the entry offsets are correct
572 This is used for entries which have extra offset requirements (other
573 than having to be fully inside their section). Sub-classes can implement
574 this function and raise if there is a problem.
582 return '%08x' % value
585 def WriteMapLine(fd, indent, name, offset, size, image_pos):
586 print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
587 Entry.GetStr(offset), Entry.GetStr(size),
590 def WriteMap(self, fd, indent):
591 """Write a map of the entry to a .map file
594 fd: File to write the map to
595 indent: Curent indent level of map (0=none, 1=one level, etc.)
597 self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
600 def GetEntries(self):
601 """Return a list of entries contained by this entry
604 List of entries, or None if none. A normal entry has no entries
605 within it so will return None
609 def GetArg(self, name, datatype=str):
610 """Get the value of an entry argument or device-tree-node property
612 Some node properties can be provided as arguments to binman. First check
613 the entry arguments, and fall back to the device tree if not found
617 datatype: Data type (str or int)
620 Value of argument as a string or int, or None if no value
623 ValueError if the argument cannot be converted to in
625 value = state.GetEntryArg(name)
626 if value is not None:
631 self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
633 elif datatype == str:
636 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
639 value = fdt_util.GetDatatype(self._node, name, datatype)
643 def WriteDocs(modules, test_missing=None):
644 """Write out documentation about the various entry types to stdout
647 modules: List of modules to include
648 test_missing: Used for testing. This is a module to report
651 print('''Binman Entry Documentation
652 ===========================
654 This file describes the entry types supported by binman. These entry types can
655 be placed in an image one by one to build up a final firmware image. It is
656 fairly easy to create new entry types. Just add a new file to the 'etype'
657 directory. You can use the existing entries as examples.
659 Note that some entries are subclasses of others, using and extending their
660 features to produce new behaviours.
664 modules = sorted(modules)
666 # Don't show the test entry
667 if '_testing' in modules:
668 modules.remove('_testing')
671 module = Entry.Lookup('WriteDocs', name, False)
672 docs = getattr(module, '__doc__')
673 if test_missing == name:
676 lines = docs.splitlines()
677 first_line = lines[0]
678 rest = [line[4:] for line in lines[1:]]
679 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
681 print('-' * len(hdr))
682 print('\n'.join(rest))
689 raise ValueError('Documentation is missing for modules: %s' %
692 def GetUniqueName(self):
693 """Get a unique name for a node
696 String containing a unique name for a node, consisting of the name
697 of all ancestors (starting from within the 'binman' node) separated
698 by a dot ('.'). This can be useful for generating unique filesnames
699 in the output directory.
705 if node.name == 'binman':
707 name = '%s.%s' % (node.name, name)
710 def ExpandToLimit(self, limit):
711 """Expand an entry so that it ends at the given offset limit"""
712 if self.offset + self.size < limit:
713 self.size = limit - self.offset
714 # Request the contents again, since changing the size requires that
715 # the data grows. This should not fail, but check it to be sure.
716 if not self.ObtainContents():
717 self.Raise('Cannot obtain contents when expanding entry')
719 def HasSibling(self, name):
720 """Check if there is a sibling of a given name
723 True if there is an entry with this name in the the same section,
726 return name in self.section.GetEntries()
728 def GetSiblingImagePos(self, name):
729 """Return the image position of the given sibling
732 Image position of sibling, or None if the sibling has no position,
733 or False if there is no such sibling
735 if not self.HasSibling(name):
737 return self.section.GetEntries()[name].image_pos
740 def AddEntryInfo(entries, indent, name, etype, size, image_pos,
741 uncomp_size, offset, entry):
742 """Add a new entry to the entries list
745 entries: List (of EntryInfo objects) to add to
746 indent: Current indent level to add to list
747 name: Entry name (string)
748 etype: Entry type (string)
749 size: Entry size in bytes (int)
750 image_pos: Position within image in bytes (int)
751 uncomp_size: Uncompressed size if the entry uses compression, else
753 offset: Entry offset within parent in bytes (int)
756 entries.append(EntryInfo(indent, name, etype, size, image_pos,
757 uncomp_size, offset, entry))
759 def ListEntries(self, entries, indent):
760 """Add files in this entry to the list of entries
762 This can be overridden by subclasses which need different behaviour.
765 entries: List (of EntryInfo objects) to add to
766 indent: Current indent level to add to list
768 self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
769 self.image_pos, self.uncomp_size, self.offset, self)
771 def ReadData(self, decomp=True):
772 """Read the data for an entry from the image
774 This is used when the image has been read in and we want to extract the
775 data for a particular entry from that image.
778 decomp: True to decompress any compressed data before returning it;
779 False to return the raw, uncompressed data
784 # Use True here so that we get an uncompressed section to work from,
785 # although compressed sections are currently not supported
786 tout.Debug("ReadChildData section '%s', entry '%s'" %
787 (self.section.GetPath(), self.GetPath()))
788 data = self.section.ReadChildData(self, decomp)
791 def ReadChildData(self, child, decomp=True):
792 """Read the data for a particular child entry
794 This reads data from the parent and extracts the piece that relates to
798 child: Child entry to read data for (must be valid)
799 decomp: True to decompress any compressed data before returning it;
800 False to return the raw, uncompressed data
803 Data for the child (bytes)
807 def LoadData(self, decomp=True):
808 data = self.ReadData(decomp)
809 self.contents_size = len(data)
810 self.ProcessContentsUpdate(data)
811 self.Detail('Loaded data size %x' % len(data))
814 """Get the image containing this entry
817 Image object containing this entry
819 return self.section.GetImage()
821 def WriteData(self, data, decomp=True):
822 """Write the data to an entry in the image
824 This is used when the image has been read in and we want to replace the
825 data for a particular entry in that image.
827 The image must be re-packed and written out afterwards.
830 data: Data to replace it with
831 decomp: True to compress the data if needed, False if data is
832 already compressed so should be used as is
835 True if the data did not result in a resize of this entry, False if
836 the entry must be resized
838 if self.size is not None:
839 self.contents_size = self.size
841 self.contents_size = self.pre_reset_size
842 ok = self.ProcessContentsUpdate(data)
843 self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
844 section_ok = self.section.WriteChildData(self)
845 return ok and section_ok
847 def WriteChildData(self, child):
848 """Handle writing the data in a child entry
850 This should be called on the child's parent section after the child's
851 data has been updated. It
853 This base-class implementation does nothing, since the base Entry object
854 does not have any children.
857 child: Child Entry that was written
860 True if the section could be updated successfully, False if the
861 data is such that the section could not updat
865 def GetSiblingOrder(self):
866 """Get the relative order of an entry amoung its siblings
869 'start' if this entry is first among siblings, 'end' if last,
872 entries = list(self.section.GetEntries().values())
874 if self == entries[0]:
876 elif self == entries[-1]:
880 def SetAllowMissing(self, allow_missing):
881 """Set whether a section allows missing external blobs
884 allow_missing: True if allowed, False if not allowed
886 # This is meaningless for anything other than sections
889 def CheckMissing(self, missing_list):
890 """Check if any entries in this section have missing external blobs
892 If there are missing blobs, the entries are added to the list
895 missing_list: List of Entry objects to be added to
898 missing_list.append(self)
900 def GetAllowMissing(self):
901 """Get whether a section allows missing external blobs
904 True if allowed, False if not allowed
906 return self.allow_missing
908 def GetHelpTags(self):
909 """Get the tags use for missing-blob help
912 list of possible tags, most desirable first
914 return list(filter(None, [self.missing_msg, self.name, self.etype]))
916 def CompressData(self, indata):
917 """Compress data according to the entry's compression method
920 indata: Data to compress
923 Compressed data (first word is the compressed size)
925 self.uncomp_data = indata
926 if self.compress != 'none':
927 self.uncomp_size = len(indata)
928 data = tools.Compress(indata, self.compress)
932 def UseExpanded(cls, node, etype, new_etype):
933 """Check whether to use an expanded entry type
935 This is called by Entry.Create() when it finds an expanded version of
936 an entry type (e.g. 'u-boot-expanded'). If this method returns True then
937 it will be used (e.g. in place of 'u-boot'). If it returns False, it is
941 node: Node object containing information about the entry to
943 etype: Original entry type being used
944 new_etype: New entry type proposed
947 True to use this entry type, False to use the original one
949 tout.Info("Node '%s': etype '%s': %s selected" %
950 (node.path, etype, new_etype))