binman: Support shrinking a entry after packing
[platform/kernel/u-boot.git] / tools / binman / entry.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 #
4 # Base class for all entries
5 #
6
7 from __future__ import print_function
8
9 from collections import namedtuple
10
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
14 try:
15     import importlib
16     have_importlib = True
17 except:
18     have_importlib = False
19
20 import os
21 import sys
22
23 import fdt_util
24 import state
25 import tools
26 from tools import ToHex, ToHexSize
27 import tout
28
29 modules = {}
30
31 our_path = os.path.dirname(os.path.realpath(__file__))
32
33
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'])
37
38 # Information about an entry for use when displaying summaries
39 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
40                                      'image_pos', 'uncomp_size', 'offset',
41                                      'entry'])
42
43 class Entry(object):
44     """An Entry in the section
45
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.
50
51     This class is not used by itself. All entry objects are subclasses of
52     Entry.
53
54     Attributes:
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
61             compressed, else None
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
72     """
73     def __init__(self, section, etype, node, name_prefix=''):
74         self.section = section
75         self.etype = etype
76         self._node = node
77         self.name = node and (name_prefix + node.name) or 'none'
78         self.offset = None
79         self.size = None
80         self.uncomp_size = None
81         self.data = None
82         self.contents_size = 0
83         self.align = None
84         self.align_size = None
85         self.align_end = None
86         self.pad_before = 0
87         self.pad_after = 0
88         self.offset_unset = False
89         self.image_pos = None
90         self._expand_size = False
91         self.compress = 'none'
92
93     @staticmethod
94     def Lookup(node_path, etype):
95         """Look up the entry class for a node.
96
97         Args:
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
101
102         Returns:
103             The entry class object if found, else None
104         """
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)
111
112         # Also allow entry-type modules to be brought in from the etype directory.
113
114         # Import the module if we have not already done so.
115         if not module:
116             old_path = sys.path
117             sys.path.insert(0, os.path.join(our_path, 'etype'))
118             try:
119                 if have_importlib:
120                     module = importlib.import_module(module_name)
121                 else:
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))
126             finally:
127                 sys.path = old_path
128             modules[module_name] = module
129
130         # Look up the expected class name
131         return getattr(module, 'Entry_%s' % module_name)
132
133     @staticmethod
134     def Create(section, node, etype=None):
135         """Create a new entry for a node.
136
137         Args:
138             section: Section object containing this node
139             node:    Node object containing information about the entry to
140                      create
141             etype:   Entry type to use, or None to work it out (used for tests)
142
143         Returns:
144             A new Entry object of the correct type (a subclass of Entry)
145         """
146         if not etype:
147             etype = fdt_util.GetString(node, 'type', node.name)
148         obj = Entry.Lookup(node.path, etype)
149
150         # Call its constructor to get the object we want.
151         return obj(section, etype, node)
152
153     def ReadNode(self):
154         """Read entry information from the node
155
156         This must be called as the first thing after the Entry is created.
157
158         This reads all the fields we recognise from the node, ready for use.
159         """
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 = fdt_util.GetInt(self._node, 'orig-offset')
165         self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
166         if self.GetImage().copy_to_orig:
167             self.orig_offset = self.offset
168             self.orig_size = self.size
169
170         # These should not be set in input files, but are set in an FDT map,
171         # which is also read by this code.
172         self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
173         self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
174
175         self.align = fdt_util.GetInt(self._node, 'align')
176         if tools.NotPowerOfTwo(self.align):
177             raise ValueError("Node '%s': Alignment %s must be a power of two" %
178                              (self._node.path, self.align))
179         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
180         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
181         self.align_size = fdt_util.GetInt(self._node, 'align-size')
182         if tools.NotPowerOfTwo(self.align_size):
183             self.Raise("Alignment size %s must be a power of two" %
184                        self.align_size)
185         self.align_end = fdt_util.GetInt(self._node, 'align-end')
186         self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
187         self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
188
189     def GetDefaultFilename(self):
190         return None
191
192     def GetFdts(self):
193         """Get the device trees used by this entry
194
195         Returns:
196             Empty dict, if this entry is not a .dtb, otherwise:
197             Dict:
198                 key: Filename from this entry (without the path)
199                 value: Tuple:
200                     Fdt object for this dtb, or None if not available
201                     Filename of file containing this dtb
202         """
203         return {}
204
205     def ExpandEntries(self):
206         pass
207
208     def AddMissingProperties(self):
209         """Add new properties to the device tree as needed for this entry"""
210         for prop in ['offset', 'size', 'image-pos']:
211             if not prop in self._node.props:
212                 state.AddZeroProp(self._node, prop)
213         if self.GetImage().allow_repack:
214             if self.orig_offset is not None:
215                 state.AddZeroProp(self._node, 'orig-offset', True)
216             if self.orig_size is not None:
217                 state.AddZeroProp(self._node, 'orig-size', True)
218
219         if self.compress != 'none':
220             state.AddZeroProp(self._node, 'uncomp-size')
221         err = state.CheckAddHashProp(self._node)
222         if err:
223             self.Raise(err)
224
225     def SetCalculatedProperties(self):
226         """Set the value of device-tree properties calculated by binman"""
227         state.SetInt(self._node, 'offset', self.offset)
228         state.SetInt(self._node, 'size', self.size)
229         base = self.section.GetRootSkipAtStart() if self.section else 0
230         state.SetInt(self._node, 'image-pos', self.image_pos - base)
231         if self.GetImage().allow_repack:
232             if self.orig_offset is not None:
233                 state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
234             if self.orig_size is not None:
235                 state.SetInt(self._node, 'orig-size', self.orig_size, True)
236         if self.uncomp_size is not None:
237             state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
238         state.CheckSetHashValue(self._node, self.GetData)
239
240     def ProcessFdt(self, fdt):
241         """Allow entries to adjust the device tree
242
243         Some entries need to adjust the device tree for their purposes. This
244         may involve adding or deleting properties.
245
246         Returns:
247             True if processing is complete
248             False if processing could not be completed due to a dependency.
249                 This will cause the entry to be retried after others have been
250                 called
251         """
252         return True
253
254     def SetPrefix(self, prefix):
255         """Set the name prefix for a node
256
257         Args:
258             prefix: Prefix to set, or '' to not use a prefix
259         """
260         if prefix:
261             self.name = prefix + self.name
262
263     def SetContents(self, data):
264         """Set the contents of an entry
265
266         This sets both the data and content_size properties
267
268         Args:
269             data: Data to set to the contents (bytes)
270         """
271         self.data = data
272         self.contents_size = len(self.data)
273
274     def ProcessContentsUpdate(self, data):
275         """Update the contents of an entry, after the size is fixed
276
277         This checks that the new data is the same size as the old. If the size
278         has changed, this triggers a re-run of the packing algorithm.
279
280         Args:
281             data: Data to set to the contents (bytes)
282
283         Raises:
284             ValueError if the new data size is not the same as the old
285         """
286         size_ok = True
287         new_size = len(data)
288         if state.AllowEntryExpansion() and new_size > self.contents_size:
289             # self.data will indicate the new size needed
290             size_ok = False
291         elif state.AllowEntryContraction() and new_size < self.contents_size:
292             size_ok = False
293
294         # If not allowed to change, try to deal with it or give up
295         if size_ok:
296             if new_size > self.contents_size:
297                 self.Raise('Cannot update entry size from %d to %d' %
298                         (self.contents_size, new_size))
299
300             # Don't let the data shrink. Pad it if necessary
301             if size_ok and new_size < self.contents_size:
302                 data += tools.GetBytes(0, self.contents_size - new_size)
303
304         if not size_ok:
305             tout.Debug("Entry '%s' size change from %s to %s" % (
306                 self._node.path, ToHex(self.contents_size),
307                 ToHex(new_size)))
308         self.SetContents(data)
309         return size_ok
310
311     def ObtainContents(self):
312         """Figure out the contents of an entry.
313
314         Returns:
315             True if the contents were found, False if another call is needed
316             after the other entries are processed.
317         """
318         # No contents by default: subclasses can implement this
319         return True
320
321     def ResetForPack(self):
322         """Reset offset/size fields so that packing can be done again"""
323         self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
324                     (ToHex(self.offset), ToHex(self.orig_offset),
325                      ToHex(self.size), ToHex(self.orig_size)))
326         self.offset = self.orig_offset
327         self.size = self.orig_size
328
329     def Pack(self, offset):
330         """Figure out how to pack the entry into the section
331
332         Most of the time the entries are not fully specified. There may be
333         an alignment but no size. In that case we take the size from the
334         contents of the entry.
335
336         If an entry has no hard-coded offset, it will be placed at @offset.
337
338         Once this function is complete, both the offset and size of the
339         entry will be know.
340
341         Args:
342             Current section offset pointer
343
344         Returns:
345             New section offset pointer (after this entry)
346         """
347         self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
348                     (ToHex(self.offset), ToHex(self.size),
349                      self.contents_size))
350         if self.offset is None:
351             if self.offset_unset:
352                 self.Raise('No offset set with offset-unset: should another '
353                            'entry provide this correct offset?')
354             self.offset = tools.Align(offset, self.align)
355         needed = self.pad_before + self.contents_size + self.pad_after
356         needed = tools.Align(needed, self.align_size)
357         size = self.size
358         if not size:
359             size = needed
360         new_offset = self.offset + size
361         aligned_offset = tools.Align(new_offset, self.align_end)
362         if aligned_offset != new_offset:
363             size = aligned_offset - self.offset
364             new_offset = aligned_offset
365
366         if not self.size:
367             self.size = size
368
369         if self.size < needed:
370             self.Raise("Entry contents size is %#x (%d) but entry size is "
371                        "%#x (%d)" % (needed, needed, self.size, self.size))
372         # Check that the alignment is correct. It could be wrong if the
373         # and offset or size values were provided (i.e. not calculated), but
374         # conflict with the provided alignment values
375         if self.size != tools.Align(self.size, self.align_size):
376             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
377                   (self.size, self.size, self.align_size, self.align_size))
378         if self.offset != tools.Align(self.offset, self.align):
379             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
380                   (self.offset, self.offset, self.align, self.align))
381         self.Detail('   - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
382                     (self.offset, self.size, self.contents_size, new_offset))
383
384         return new_offset
385
386     def Raise(self, msg):
387         """Convenience function to raise an error referencing a node"""
388         raise ValueError("Node '%s': %s" % (self._node.path, msg))
389
390     def Detail(self, msg):
391         """Convenience function to log detail referencing a node"""
392         tag = "Node '%s'" % self._node.path
393         tout.Detail('%30s: %s' % (tag, msg))
394
395     def GetEntryArgsOrProps(self, props, required=False):
396         """Return the values of a set of properties
397
398         Args:
399             props: List of EntryArg objects
400
401         Raises:
402             ValueError if a property is not found
403         """
404         values = []
405         missing = []
406         for prop in props:
407             python_prop = prop.name.replace('-', '_')
408             if hasattr(self, python_prop):
409                 value = getattr(self, python_prop)
410             else:
411                 value = None
412             if value is None:
413                 value = self.GetArg(prop.name, prop.datatype)
414             if value is None and required:
415                 missing.append(prop.name)
416             values.append(value)
417         if missing:
418             self.Raise('Missing required properties/entry args: %s' %
419                        (', '.join(missing)))
420         return values
421
422     def GetPath(self):
423         """Get the path of a node
424
425         Returns:
426             Full path of the node for this entry
427         """
428         return self._node.path
429
430     def GetData(self):
431         self.Detail('GetData: size %s' % ToHexSize(self.data))
432         return self.data
433
434     def GetOffsets(self):
435         """Get the offsets for siblings
436
437         Some entry types can contain information about the position or size of
438         other entries. An example of this is the Intel Flash Descriptor, which
439         knows where the Intel Management Engine section should go.
440
441         If this entry knows about the position of other entries, it can specify
442         this by returning values here
443
444         Returns:
445             Dict:
446                 key: Entry type
447                 value: List containing position and size of the given entry
448                     type. Either can be None if not known
449         """
450         return {}
451
452     def SetOffsetSize(self, offset, size):
453         """Set the offset and/or size of an entry
454
455         Args:
456             offset: New offset, or None to leave alone
457             size: New size, or None to leave alone
458         """
459         if offset is not None:
460             self.offset = offset
461         if size is not None:
462             self.size = size
463
464     def SetImagePos(self, image_pos):
465         """Set the position in the image
466
467         Args:
468             image_pos: Position of this entry in the image
469         """
470         self.image_pos = image_pos + self.offset
471
472     def ProcessContents(self):
473         """Do any post-packing updates of entry contents
474
475         This function should call ProcessContentsUpdate() to update the entry
476         contents, if necessary, returning its return value here.
477
478         Args:
479             data: Data to set to the contents (bytes)
480
481         Returns:
482             True if the new data size is OK, False if expansion is needed
483
484         Raises:
485             ValueError if the new data size is not the same as the old and
486                 state.AllowEntryExpansion() is False
487         """
488         return True
489
490     def WriteSymbols(self, section):
491         """Write symbol values into binary files for access at run time
492
493         Args:
494           section: Section containing the entry
495         """
496         pass
497
498     def CheckOffset(self):
499         """Check that the entry offsets are correct
500
501         This is used for entries which have extra offset requirements (other
502         than having to be fully inside their section). Sub-classes can implement
503         this function and raise if there is a problem.
504         """
505         pass
506
507     @staticmethod
508     def GetStr(value):
509         if value is None:
510             return '<none>  '
511         return '%08x' % value
512
513     @staticmethod
514     def WriteMapLine(fd, indent, name, offset, size, image_pos):
515         print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
516                                     Entry.GetStr(offset), Entry.GetStr(size),
517                                     name), file=fd)
518
519     def WriteMap(self, fd, indent):
520         """Write a map of the entry to a .map file
521
522         Args:
523             fd: File to write the map to
524             indent: Curent indent level of map (0=none, 1=one level, etc.)
525         """
526         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
527                           self.image_pos)
528
529     def GetEntries(self):
530         """Return a list of entries contained by this entry
531
532         Returns:
533             List of entries, or None if none. A normal entry has no entries
534                 within it so will return None
535         """
536         return None
537
538     def GetArg(self, name, datatype=str):
539         """Get the value of an entry argument or device-tree-node property
540
541         Some node properties can be provided as arguments to binman. First check
542         the entry arguments, and fall back to the device tree if not found
543
544         Args:
545             name: Argument name
546             datatype: Data type (str or int)
547
548         Returns:
549             Value of argument as a string or int, or None if no value
550
551         Raises:
552             ValueError if the argument cannot be converted to in
553         """
554         value = state.GetEntryArg(name)
555         if value is not None:
556             if datatype == int:
557                 try:
558                     value = int(value)
559                 except ValueError:
560                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
561                                (name, value))
562             elif datatype == str:
563                 pass
564             else:
565                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
566                                  datatype)
567         else:
568             value = fdt_util.GetDatatype(self._node, name, datatype)
569         return value
570
571     @staticmethod
572     def WriteDocs(modules, test_missing=None):
573         """Write out documentation about the various entry types to stdout
574
575         Args:
576             modules: List of modules to include
577             test_missing: Used for testing. This is a module to report
578                 as missing
579         """
580         print('''Binman Entry Documentation
581 ===========================
582
583 This file describes the entry types supported by binman. These entry types can
584 be placed in an image one by one to build up a final firmware image. It is
585 fairly easy to create new entry types. Just add a new file to the 'etype'
586 directory. You can use the existing entries as examples.
587
588 Note that some entries are subclasses of others, using and extending their
589 features to produce new behaviours.
590
591
592 ''')
593         modules = sorted(modules)
594
595         # Don't show the test entry
596         if '_testing' in modules:
597             modules.remove('_testing')
598         missing = []
599         for name in modules:
600             if name.startswith('__'):
601                 continue
602             module = Entry.Lookup(name, name)
603             docs = getattr(module, '__doc__')
604             if test_missing == name:
605                 docs = None
606             if docs:
607                 lines = docs.splitlines()
608                 first_line = lines[0]
609                 rest = [line[4:] for line in lines[1:]]
610                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
611                 print(hdr)
612                 print('-' * len(hdr))
613                 print('\n'.join(rest))
614                 print()
615                 print()
616             else:
617                 missing.append(name)
618
619         if missing:
620             raise ValueError('Documentation is missing for modules: %s' %
621                              ', '.join(missing))
622
623     def GetUniqueName(self):
624         """Get a unique name for a node
625
626         Returns:
627             String containing a unique name for a node, consisting of the name
628             of all ancestors (starting from within the 'binman' node) separated
629             by a dot ('.'). This can be useful for generating unique filesnames
630             in the output directory.
631         """
632         name = self.name
633         node = self._node
634         while node.parent:
635             node = node.parent
636             if node.name == 'binman':
637                 break
638             name = '%s.%s' % (node.name, name)
639         return name
640
641     def ExpandToLimit(self, limit):
642         """Expand an entry so that it ends at the given offset limit"""
643         if self.offset + self.size < limit:
644             self.size = limit - self.offset
645             # Request the contents again, since changing the size requires that
646             # the data grows. This should not fail, but check it to be sure.
647             if not self.ObtainContents():
648                 self.Raise('Cannot obtain contents when expanding entry')
649
650     def HasSibling(self, name):
651         """Check if there is a sibling of a given name
652
653         Returns:
654             True if there is an entry with this name in the the same section,
655                 else False
656         """
657         return name in self.section.GetEntries()
658
659     def GetSiblingImagePos(self, name):
660         """Return the image position of the given sibling
661
662         Returns:
663             Image position of sibling, or None if the sibling has no position,
664                 or False if there is no such sibling
665         """
666         if not self.HasSibling(name):
667             return False
668         return self.section.GetEntries()[name].image_pos
669
670     @staticmethod
671     def AddEntryInfo(entries, indent, name, etype, size, image_pos,
672                      uncomp_size, offset, entry):
673         """Add a new entry to the entries list
674
675         Args:
676             entries: List (of EntryInfo objects) to add to
677             indent: Current indent level to add to list
678             name: Entry name (string)
679             etype: Entry type (string)
680             size: Entry size in bytes (int)
681             image_pos: Position within image in bytes (int)
682             uncomp_size: Uncompressed size if the entry uses compression, else
683                 None
684             offset: Entry offset within parent in bytes (int)
685             entry: Entry object
686         """
687         entries.append(EntryInfo(indent, name, etype, size, image_pos,
688                                  uncomp_size, offset, entry))
689
690     def ListEntries(self, entries, indent):
691         """Add files in this entry to the list of entries
692
693         This can be overridden by subclasses which need different behaviour.
694
695         Args:
696             entries: List (of EntryInfo objects) to add to
697             indent: Current indent level to add to list
698         """
699         self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
700                           self.image_pos, self.uncomp_size, self.offset, self)
701
702     def ReadData(self, decomp=True):
703         """Read the data for an entry from the image
704
705         This is used when the image has been read in and we want to extract the
706         data for a particular entry from that image.
707
708         Args:
709             decomp: True to decompress any compressed data before returning it;
710                 False to return the raw, uncompressed data
711
712         Returns:
713             Entry data (bytes)
714         """
715         # Use True here so that we get an uncompressed section to work from,
716         # although compressed sections are currently not supported
717         data = self.section.ReadData(True)
718         tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
719                   (self.GetPath(), self.offset, self.offset + self.size,
720                    self.size, len(data)))
721         return data[self.offset:self.offset + self.size]
722
723     def LoadData(self, decomp=True):
724         data = self.ReadData(decomp)
725         self.contents_size = len(data)
726         self.ProcessContentsUpdate(data)
727         self.Detail('Loaded data size %x' % len(data))
728
729     def GetImage(self):
730         """Get the image containing this entry
731
732         Returns:
733             Image object containing this entry
734         """
735         return self.section.GetImage()
736
737     def WriteData(self, data, decomp=True):
738         """Write the data to an entry in the image
739
740         This is used when the image has been read in and we want to replace the
741         data for a particular entry in that image.
742
743         The image must be re-packed and written out afterwards.
744
745         Args:
746             data: Data to replace it with
747             decomp: True to compress the data if needed, False if data is
748                 already compressed so should be used as is
749
750         Returns:
751             True if the data did not result in a resize of this entry, False if
752                  the entry must be resized
753         """
754         self.contents_size = self.size
755         ok = self.ProcessContentsUpdate(data)
756         self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
757         return ok
758
759     def GetSiblingOrder(self):
760         """Get the relative order of an entry amoung its siblings
761
762         Returns:
763             'start' if this entry is first among siblings, 'end' if last,
764                 otherwise None
765         """
766         entries = list(self.section.GetEntries().values())
767         if entries:
768             if self == entries[0]:
769                 return 'start'
770             elif self == entries[-1]:
771                 return 'end'
772         return 'middle'