1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
4 # Base class for all entries
7 from __future__ import print_function
9 from collections import namedtuple
11 # importlib was introduced in Python 2.7 but there was a report of it not
12 # working in 2.7.12, so we work around this:
13 # http://lists.denx.de/pipermail/u-boot/2016-October/269729.html
18 have_importlib = False
26 from tools import ToHex, ToHexSize
31 our_path = os.path.dirname(os.path.realpath(__file__))
34 # An argument which can be passed to entries on the command line, in lieu of
35 # device-tree properties.
36 EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
38 # Information about an entry for use when displaying summaries
39 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
40 'image_pos', 'uncomp_size', 'offset',
44 """An Entry in the section
46 An entry corresponds to a single node in the device-tree description
47 of the section. Each entry ends up being a part of the final section.
48 Entries can be placed either right next to each other, or with padding
49 between them. The type of the entry determines the data that is in it.
51 This class is not used by itself. All entry objects are subclasses of
55 section: Section object containing this entry
56 node: The node that created this entry
57 offset: Offset of entry within the section, None if not known yet (in
58 which case it will be calculated by Pack())
59 size: Entry size in bytes, None if not known
60 uncomp_size: Size of uncompressed data in bytes, if the entry is
62 contents_size: Size of contents in bytes, 0 by default
63 align: Entry start offset alignment, or None
64 align_size: Entry size alignment, or None
65 align_end: Entry end offset alignment, or None
66 pad_before: Number of pad bytes before the contents, 0 if none
67 pad_after: Number of pad bytes after the contents, 0 if none
68 data: Contents of entry (string of bytes)
69 compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
70 orig_offset: Original offset value read from node
71 orig_size: Original size value read from node
73 def __init__(self, section, etype, node, name_prefix=''):
74 self.section = section
77 self.name = node and (name_prefix + node.name) or 'none'
80 self.uncomp_size = None
82 self.contents_size = 0
84 self.align_size = None
88 self.offset_unset = False
90 self._expand_size = False
91 self.compress = 'none'
94 def Lookup(node_path, etype):
95 """Look up the entry class for a node.
98 node_node: Path name of Node object containing information about
99 the entry to create (used for errors)
100 etype: Entry type to use
103 The entry class object if found, else None
105 # Convert something like 'u-boot@0' to 'u_boot' since we are only
106 # interested in the type.
107 module_name = etype.replace('-', '_')
108 if '@' in module_name:
109 module_name = module_name.split('@')[0]
110 module = modules.get(module_name)
112 # Also allow entry-type modules to be brought in from the etype directory.
114 # Import the module if we have not already done so.
117 sys.path.insert(0, os.path.join(our_path, 'etype'))
120 module = importlib.import_module(module_name)
122 module = __import__(module_name)
123 except ImportError as e:
124 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
125 (etype, node_path, module_name, e))
128 modules[module_name] = module
130 # Look up the expected class name
131 return getattr(module, 'Entry_%s' % module_name)
134 def Create(section, node, etype=None):
135 """Create a new entry for a node.
138 section: Section object containing this node
139 node: Node object containing information about the entry to
141 etype: Entry type to use, or None to work it out (used for tests)
144 A new Entry object of the correct type (a subclass of Entry)
147 etype = fdt_util.GetString(node, 'type', node.name)
148 obj = Entry.Lookup(node.path, etype)
150 # Call its constructor to get the object we want.
151 return obj(section, etype, node)
154 """Read entry information from the node
156 This must be called as the first thing after the Entry is created.
158 This reads all the fields we recognise from the node, ready for use.
160 if 'pos' in self._node.props:
161 self.Raise("Please use 'offset' instead of 'pos'")
162 self.offset = fdt_util.GetInt(self._node, 'offset')
163 self.size = fdt_util.GetInt(self._node, 'size')
164 self.orig_offset = self.offset
165 self.orig_size = self.size
167 # These should not be set in input files, but are set in an FDT map,
168 # which is also read by this code.
169 self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
170 self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
172 self.align = fdt_util.GetInt(self._node, 'align')
173 if tools.NotPowerOfTwo(self.align):
174 raise ValueError("Node '%s': Alignment %s must be a power of two" %
175 (self._node.path, self.align))
176 self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
177 self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
178 self.align_size = fdt_util.GetInt(self._node, 'align-size')
179 if tools.NotPowerOfTwo(self.align_size):
180 self.Raise("Alignment size %s must be a power of two" %
182 self.align_end = fdt_util.GetInt(self._node, 'align-end')
183 self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
184 self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
186 def GetDefaultFilename(self):
190 """Get the device trees used by this entry
193 Empty dict, if this entry is not a .dtb, otherwise:
195 key: Filename from this entry (without the path)
197 Fdt object for this dtb, or None if not available
198 Filename of file containing this dtb
202 def ExpandEntries(self):
205 def AddMissingProperties(self):
206 """Add new properties to the device tree as needed for this entry"""
207 for prop in ['offset', 'size', 'image-pos']:
208 if not prop in self._node.props:
209 state.AddZeroProp(self._node, prop)
210 if self.compress != 'none':
211 state.AddZeroProp(self._node, 'uncomp-size')
212 err = state.CheckAddHashProp(self._node)
216 def SetCalculatedProperties(self):
217 """Set the value of device-tree properties calculated by binman"""
218 state.SetInt(self._node, 'offset', self.offset)
219 state.SetInt(self._node, 'size', self.size)
220 base = self.section.GetRootSkipAtStart() if self.section else 0
221 state.SetInt(self._node, 'image-pos', self.image_pos - base)
222 if self.uncomp_size is not None:
223 state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
224 state.CheckSetHashValue(self._node, self.GetData)
226 def ProcessFdt(self, fdt):
227 """Allow entries to adjust the device tree
229 Some entries need to adjust the device tree for their purposes. This
230 may involve adding or deleting properties.
233 True if processing is complete
234 False if processing could not be completed due to a dependency.
235 This will cause the entry to be retried after others have been
240 def SetPrefix(self, prefix):
241 """Set the name prefix for a node
244 prefix: Prefix to set, or '' to not use a prefix
247 self.name = prefix + self.name
249 def SetContents(self, data):
250 """Set the contents of an entry
252 This sets both the data and content_size properties
255 data: Data to set to the contents (bytes)
258 self.contents_size = len(self.data)
260 def ProcessContentsUpdate(self, data):
261 """Update the contents of an entry, after the size is fixed
263 This checks that the new data is the same size as the old. If the size
264 has changed, this triggers a re-run of the packing algorithm.
267 data: Data to set to the contents (bytes)
270 ValueError if the new data size is not the same as the old
274 if state.AllowEntryExpansion():
275 if new_size > self.contents_size:
276 tout.Debug("Entry '%s' size change from %s to %s" % (
277 self._node.path, ToHex(self.contents_size),
279 # self.data will indicate the new size needed
281 elif new_size != self.contents_size:
282 self.Raise('Cannot update entry size from %d to %d' %
283 (self.contents_size, new_size))
284 self.SetContents(data)
287 def ObtainContents(self):
288 """Figure out the contents of an entry.
291 True if the contents were found, False if another call is needed
292 after the other entries are processed.
294 # No contents by default: subclasses can implement this
297 def ResetForPack(self):
298 """Reset offset/size fields so that packing can be done again"""
299 self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
300 (ToHex(self.offset), ToHex(self.orig_offset),
301 ToHex(self.size), ToHex(self.orig_size)))
302 self.offset = self.orig_offset
303 self.size = self.orig_size
305 def Pack(self, offset):
306 """Figure out how to pack the entry into the section
308 Most of the time the entries are not fully specified. There may be
309 an alignment but no size. In that case we take the size from the
310 contents of the entry.
312 If an entry has no hard-coded offset, it will be placed at @offset.
314 Once this function is complete, both the offset and size of the
318 Current section offset pointer
321 New section offset pointer (after this entry)
323 self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
324 (ToHex(self.offset), ToHex(self.size),
326 if self.offset is None:
327 if self.offset_unset:
328 self.Raise('No offset set with offset-unset: should another '
329 'entry provide this correct offset?')
330 self.offset = tools.Align(offset, self.align)
331 needed = self.pad_before + self.contents_size + self.pad_after
332 needed = tools.Align(needed, self.align_size)
336 new_offset = self.offset + size
337 aligned_offset = tools.Align(new_offset, self.align_end)
338 if aligned_offset != new_offset:
339 size = aligned_offset - self.offset
340 new_offset = aligned_offset
345 if self.size < needed:
346 self.Raise("Entry contents size is %#x (%d) but entry size is "
347 "%#x (%d)" % (needed, needed, self.size, self.size))
348 # Check that the alignment is correct. It could be wrong if the
349 # and offset or size values were provided (i.e. not calculated), but
350 # conflict with the provided alignment values
351 if self.size != tools.Align(self.size, self.align_size):
352 self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
353 (self.size, self.size, self.align_size, self.align_size))
354 if self.offset != tools.Align(self.offset, self.align):
355 self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
356 (self.offset, self.offset, self.align, self.align))
357 self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
358 (self.offset, self.size, self.contents_size, new_offset))
362 def Raise(self, msg):
363 """Convenience function to raise an error referencing a node"""
364 raise ValueError("Node '%s': %s" % (self._node.path, msg))
366 def Detail(self, msg):
367 """Convenience function to log detail referencing a node"""
368 tag = "Node '%s'" % self._node.path
369 tout.Detail('%30s: %s' % (tag, msg))
371 def GetEntryArgsOrProps(self, props, required=False):
372 """Return the values of a set of properties
375 props: List of EntryArg objects
378 ValueError if a property is not found
383 python_prop = prop.name.replace('-', '_')
384 if hasattr(self, python_prop):
385 value = getattr(self, python_prop)
389 value = self.GetArg(prop.name, prop.datatype)
390 if value is None and required:
391 missing.append(prop.name)
394 self.Raise('Missing required properties/entry args: %s' %
395 (', '.join(missing)))
399 """Get the path of a node
402 Full path of the node for this entry
404 return self._node.path
407 self.Detail('GetData: size %s' % ToHexSize(self.data))
410 def GetOffsets(self):
411 """Get the offsets for siblings
413 Some entry types can contain information about the position or size of
414 other entries. An example of this is the Intel Flash Descriptor, which
415 knows where the Intel Management Engine section should go.
417 If this entry knows about the position of other entries, it can specify
418 this by returning values here
423 value: List containing position and size of the given entry
424 type. Either can be None if not known
428 def SetOffsetSize(self, offset, size):
429 """Set the offset and/or size of an entry
432 offset: New offset, or None to leave alone
433 size: New size, or None to leave alone
435 if offset is not None:
440 def SetImagePos(self, image_pos):
441 """Set the position in the image
444 image_pos: Position of this entry in the image
446 self.image_pos = image_pos + self.offset
448 def ProcessContents(self):
449 """Do any post-packing updates of entry contents
451 This function should call ProcessContentsUpdate() to update the entry
452 contents, if necessary, returning its return value here.
455 data: Data to set to the contents (bytes)
458 True if the new data size is OK, False if expansion is needed
461 ValueError if the new data size is not the same as the old and
462 state.AllowEntryExpansion() is False
466 def WriteSymbols(self, section):
467 """Write symbol values into binary files for access at run time
470 section: Section containing the entry
474 def CheckOffset(self):
475 """Check that the entry offsets are correct
477 This is used for entries which have extra offset requirements (other
478 than having to be fully inside their section). Sub-classes can implement
479 this function and raise if there is a problem.
487 return '%08x' % value
490 def WriteMapLine(fd, indent, name, offset, size, image_pos):
491 print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
492 Entry.GetStr(offset), Entry.GetStr(size),
495 def WriteMap(self, fd, indent):
496 """Write a map of the entry to a .map file
499 fd: File to write the map to
500 indent: Curent indent level of map (0=none, 1=one level, etc.)
502 self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
505 def GetEntries(self):
506 """Return a list of entries contained by this entry
509 List of entries, or None if none. A normal entry has no entries
510 within it so will return None
514 def GetArg(self, name, datatype=str):
515 """Get the value of an entry argument or device-tree-node property
517 Some node properties can be provided as arguments to binman. First check
518 the entry arguments, and fall back to the device tree if not found
522 datatype: Data type (str or int)
525 Value of argument as a string or int, or None if no value
528 ValueError if the argument cannot be converted to in
530 value = state.GetEntryArg(name)
531 if value is not None:
536 self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
538 elif datatype == str:
541 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
544 value = fdt_util.GetDatatype(self._node, name, datatype)
548 def WriteDocs(modules, test_missing=None):
549 """Write out documentation about the various entry types to stdout
552 modules: List of modules to include
553 test_missing: Used for testing. This is a module to report
556 print('''Binman Entry Documentation
557 ===========================
559 This file describes the entry types supported by binman. These entry types can
560 be placed in an image one by one to build up a final firmware image. It is
561 fairly easy to create new entry types. Just add a new file to the 'etype'
562 directory. You can use the existing entries as examples.
564 Note that some entries are subclasses of others, using and extending their
565 features to produce new behaviours.
569 modules = sorted(modules)
571 # Don't show the test entry
572 if '_testing' in modules:
573 modules.remove('_testing')
576 if name.startswith('__'):
578 module = Entry.Lookup(name, name)
579 docs = getattr(module, '__doc__')
580 if test_missing == name:
583 lines = docs.splitlines()
584 first_line = lines[0]
585 rest = [line[4:] for line in lines[1:]]
586 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
588 print('-' * len(hdr))
589 print('\n'.join(rest))
596 raise ValueError('Documentation is missing for modules: %s' %
599 def GetUniqueName(self):
600 """Get a unique name for a node
603 String containing a unique name for a node, consisting of the name
604 of all ancestors (starting from within the 'binman' node) separated
605 by a dot ('.'). This can be useful for generating unique filesnames
606 in the output directory.
612 if node.name == 'binman':
614 name = '%s.%s' % (node.name, name)
617 def ExpandToLimit(self, limit):
618 """Expand an entry so that it ends at the given offset limit"""
619 if self.offset + self.size < limit:
620 self.size = limit - self.offset
621 # Request the contents again, since changing the size requires that
622 # the data grows. This should not fail, but check it to be sure.
623 if not self.ObtainContents():
624 self.Raise('Cannot obtain contents when expanding entry')
626 def HasSibling(self, name):
627 """Check if there is a sibling of a given name
630 True if there is an entry with this name in the the same section,
633 return name in self.section.GetEntries()
635 def GetSiblingImagePos(self, name):
636 """Return the image position of the given sibling
639 Image position of sibling, or None if the sibling has no position,
640 or False if there is no such sibling
642 if not self.HasSibling(name):
644 return self.section.GetEntries()[name].image_pos
647 def AddEntryInfo(entries, indent, name, etype, size, image_pos,
648 uncomp_size, offset, entry):
649 """Add a new entry to the entries list
652 entries: List (of EntryInfo objects) to add to
653 indent: Current indent level to add to list
654 name: Entry name (string)
655 etype: Entry type (string)
656 size: Entry size in bytes (int)
657 image_pos: Position within image in bytes (int)
658 uncomp_size: Uncompressed size if the entry uses compression, else
660 offset: Entry offset within parent in bytes (int)
663 entries.append(EntryInfo(indent, name, etype, size, image_pos,
664 uncomp_size, offset, entry))
666 def ListEntries(self, entries, indent):
667 """Add files in this entry to the list of entries
669 This can be overridden by subclasses which need different behaviour.
672 entries: List (of EntryInfo objects) to add to
673 indent: Current indent level to add to list
675 self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
676 self.image_pos, self.uncomp_size, self.offset, self)
678 def ReadData(self, decomp=True):
679 """Read the data for an entry from the image
681 This is used when the image has been read in and we want to extract the
682 data for a particular entry from that image.
685 decomp: True to decompress any compressed data before returning it;
686 False to return the raw, uncompressed data
691 # Use True here so that we get an uncompressed section to work from,
692 # although compressed sections are currently not supported
693 data = self.section.ReadData(True)
694 tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
695 (self.GetPath(), self.offset, self.offset + self.size,
696 self.size, len(data)))
697 return data[self.offset:self.offset + self.size]
699 def LoadData(self, decomp=True):
700 data = self.ReadData(decomp)
701 self.contents_size = len(data)
702 self.ProcessContentsUpdate(data)
703 self.Detail('Loaded data size %x' % len(data))
706 """Get the image containing this entry
709 Image object containing this entry
711 return self.section.GetImage()
713 def WriteData(self, data, decomp=True):
714 """Write the data to an entry in the image
716 This is used when the image has been read in and we want to replace the
717 data for a particular entry in that image.
719 The image must be re-packed and written out afterwards.
722 data: Data to replace it with
723 decomp: True to compress the data if needed, False if data is
724 already compressed so should be used as is
727 True if the data did not result in a resize of this entry, False if
728 the entry must be resized
730 self.contents_size = self.size
731 ok = self.ProcessContentsUpdate(data)
732 self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))