Merge tag 'dm-pull-24jul19-take3' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[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 import tout
27
28 modules = {}
29
30 our_path = os.path.dirname(os.path.realpath(__file__))
31
32
33 # An argument which can be passed to entries on the command line, in lieu of
34 # device-tree properties.
35 EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
36
37 # Information about an entry for use when displaying summaries
38 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
39                                      'image_pos', 'uncomp_size', 'offset',
40                                      'entry'])
41
42 class Entry(object):
43     """An Entry in the section
44
45     An entry corresponds to a single node in the device-tree description
46     of the section. Each entry ends up being a part of the final section.
47     Entries can be placed either right next to each other, or with padding
48     between them. The type of the entry determines the data that is in it.
49
50     This class is not used by itself. All entry objects are subclasses of
51     Entry.
52
53     Attributes:
54         section: Section object containing this entry
55         node: The node that created this entry
56         offset: Offset of entry within the section, None if not known yet (in
57             which case it will be calculated by Pack())
58         size: Entry size in bytes, None if not known
59         uncomp_size: Size of uncompressed data in bytes, if the entry is
60             compressed, else None
61         contents_size: Size of contents in bytes, 0 by default
62         align: Entry start offset alignment, or None
63         align_size: Entry size alignment, or None
64         align_end: Entry end offset alignment, or None
65         pad_before: Number of pad bytes before the contents, 0 if none
66         pad_after: Number of pad bytes after the contents, 0 if none
67         data: Contents of entry (string of bytes)
68         compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
69         orig_offset: Original offset value read from node
70         orig_size: Original size value read from node
71     """
72     def __init__(self, section, etype, node, read_node=True, name_prefix=''):
73         self.section = section
74         self.etype = etype
75         self._node = node
76         self.name = node and (name_prefix + node.name) or 'none'
77         self.offset = None
78         self.size = None
79         self.uncomp_size = None
80         self.data = None
81         self.contents_size = 0
82         self.align = None
83         self.align_size = None
84         self.align_end = None
85         self.pad_before = 0
86         self.pad_after = 0
87         self.offset_unset = False
88         self.image_pos = None
89         self._expand_size = False
90         self.compress = 'none'
91         if read_node:
92             self.ReadNode()
93
94     @staticmethod
95     def Lookup(node_path, etype):
96         """Look up the entry class for a node.
97
98         Args:
99             node_node: Path name of Node object containing information about
100                        the entry to create (used for errors)
101             etype:   Entry type to use
102
103         Returns:
104             The entry class object if found, else None
105         """
106         # Convert something like 'u-boot@0' to 'u_boot' since we are only
107         # interested in the type.
108         module_name = etype.replace('-', '_')
109         if '@' in module_name:
110             module_name = module_name.split('@')[0]
111         module = modules.get(module_name)
112
113         # Also allow entry-type modules to be brought in from the etype directory.
114
115         # Import the module if we have not already done so.
116         if not module:
117             old_path = sys.path
118             sys.path.insert(0, os.path.join(our_path, 'etype'))
119             try:
120                 if have_importlib:
121                     module = importlib.import_module(module_name)
122                 else:
123                     module = __import__(module_name)
124             except ImportError as e:
125                 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
126                                  (etype, node_path, module_name, e))
127             finally:
128                 sys.path = old_path
129             modules[module_name] = module
130
131         # Look up the expected class name
132         return getattr(module, 'Entry_%s' % module_name)
133
134     @staticmethod
135     def Create(section, node, etype=None):
136         """Create a new entry for a node.
137
138         Args:
139             section: Section object containing this node
140             node:    Node object containing information about the entry to
141                      create
142             etype:   Entry type to use, or None to work it out (used for tests)
143
144         Returns:
145             A new Entry object of the correct type (a subclass of Entry)
146         """
147         if not etype:
148             etype = fdt_util.GetString(node, 'type', node.name)
149         obj = Entry.Lookup(node.path, etype)
150
151         # Call its constructor to get the object we want.
152         return obj(section, etype, node)
153
154     def ReadNode(self):
155         """Read entry information from the node
156
157         This reads all the fields we recognise from the node, ready for use.
158         """
159         if 'pos' in self._node.props:
160             self.Raise("Please use 'offset' instead of 'pos'")
161         self.offset = fdt_util.GetInt(self._node, 'offset')
162         self.size = fdt_util.GetInt(self._node, 'size')
163         self.orig_offset = self.offset
164         self.orig_size = self.size
165
166         # These should not be set in input files, but are set in an FDT map,
167         # which is also read by this code.
168         self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
169         self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
170
171         self.align = fdt_util.GetInt(self._node, 'align')
172         if tools.NotPowerOfTwo(self.align):
173             raise ValueError("Node '%s': Alignment %s must be a power of two" %
174                              (self._node.path, self.align))
175         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
176         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
177         self.align_size = fdt_util.GetInt(self._node, 'align-size')
178         if tools.NotPowerOfTwo(self.align_size):
179             self.Raise("Alignment size %s must be a power of two" %
180                        self.align_size)
181         self.align_end = fdt_util.GetInt(self._node, 'align-end')
182         self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
183         self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
184
185     def GetDefaultFilename(self):
186         return None
187
188     def GetFdtSet(self):
189         """Get the set of device trees used by this entry
190
191         Returns:
192             Set containing the filename from this entry, if it is a .dtb, else
193             an empty set
194         """
195         fname = self.GetDefaultFilename()
196         # It would be better to use isinstance(self, Entry_blob_dtb) here but
197         # we cannot access Entry_blob_dtb
198         if fname and fname.endswith('.dtb'):
199             return set([fname])
200         return set()
201
202     def ExpandEntries(self):
203         pass
204
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)
213         if err:
214             self.Raise(err)
215
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)
225
226     def ProcessFdt(self, fdt):
227         """Allow entries to adjust the device tree
228
229         Some entries need to adjust the device tree for their purposes. This
230         may involve adding or deleting properties.
231
232         Returns:
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
236                 called
237         """
238         return True
239
240     def SetPrefix(self, prefix):
241         """Set the name prefix for a node
242
243         Args:
244             prefix: Prefix to set, or '' to not use a prefix
245         """
246         if prefix:
247             self.name = prefix + self.name
248
249     def SetContents(self, data):
250         """Set the contents of an entry
251
252         This sets both the data and content_size properties
253
254         Args:
255             data: Data to set to the contents (bytes)
256         """
257         self.data = data
258         self.contents_size = len(self.data)
259
260     def ProcessContentsUpdate(self, data):
261         """Update the contents of an entry, after the size is fixed
262
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.
265
266         Args:
267             data: Data to set to the contents (bytes)
268
269         Raises:
270             ValueError if the new data size is not the same as the old
271         """
272         size_ok = True
273         new_size = len(data)
274         if state.AllowEntryExpansion():
275             if new_size > self.contents_size:
276                 tout.Debug("Entry '%s' size change from %#x to %#x" % (
277                     self._node.path, self.contents_size, new_size))
278                 # self.data will indicate the new size needed
279                 size_ok = False
280         elif new_size != self.contents_size:
281             self.Raise('Cannot update entry size from %d to %d' %
282                        (self.contents_size, new_size))
283         self.SetContents(data)
284         return size_ok
285
286     def ObtainContents(self):
287         """Figure out the contents of an entry.
288
289         Returns:
290             True if the contents were found, False if another call is needed
291             after the other entries are processed.
292         """
293         # No contents by default: subclasses can implement this
294         return True
295
296     def ResetForPack(self):
297         """Reset offset/size fields so that packing can be done again"""
298         self.offset = self.orig_offset
299         self.size = self.orig_size
300
301     def Pack(self, offset):
302         """Figure out how to pack the entry into the section
303
304         Most of the time the entries are not fully specified. There may be
305         an alignment but no size. In that case we take the size from the
306         contents of the entry.
307
308         If an entry has no hard-coded offset, it will be placed at @offset.
309
310         Once this function is complete, both the offset and size of the
311         entry will be know.
312
313         Args:
314             Current section offset pointer
315
316         Returns:
317             New section offset pointer (after this entry)
318         """
319         if self.offset is None:
320             if self.offset_unset:
321                 self.Raise('No offset set with offset-unset: should another '
322                            'entry provide this correct offset?')
323             self.offset = tools.Align(offset, self.align)
324         needed = self.pad_before + self.contents_size + self.pad_after
325         needed = tools.Align(needed, self.align_size)
326         size = self.size
327         if not size:
328             size = needed
329         new_offset = self.offset + size
330         aligned_offset = tools.Align(new_offset, self.align_end)
331         if aligned_offset != new_offset:
332             size = aligned_offset - self.offset
333             new_offset = aligned_offset
334
335         if not self.size:
336             self.size = size
337
338         if self.size < needed:
339             self.Raise("Entry contents size is %#x (%d) but entry size is "
340                        "%#x (%d)" % (needed, needed, self.size, self.size))
341         # Check that the alignment is correct. It could be wrong if the
342         # and offset or size values were provided (i.e. not calculated), but
343         # conflict with the provided alignment values
344         if self.size != tools.Align(self.size, self.align_size):
345             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
346                   (self.size, self.size, self.align_size, self.align_size))
347         if self.offset != tools.Align(self.offset, self.align):
348             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
349                   (self.offset, self.offset, self.align, self.align))
350
351         return new_offset
352
353     def Raise(self, msg):
354         """Convenience function to raise an error referencing a node"""
355         raise ValueError("Node '%s': %s" % (self._node.path, msg))
356
357     def GetEntryArgsOrProps(self, props, required=False):
358         """Return the values of a set of properties
359
360         Args:
361             props: List of EntryArg objects
362
363         Raises:
364             ValueError if a property is not found
365         """
366         values = []
367         missing = []
368         for prop in props:
369             python_prop = prop.name.replace('-', '_')
370             if hasattr(self, python_prop):
371                 value = getattr(self, python_prop)
372             else:
373                 value = None
374             if value is None:
375                 value = self.GetArg(prop.name, prop.datatype)
376             if value is None and required:
377                 missing.append(prop.name)
378             values.append(value)
379         if missing:
380             self.Raise('Missing required properties/entry args: %s' %
381                        (', '.join(missing)))
382         return values
383
384     def GetPath(self):
385         """Get the path of a node
386
387         Returns:
388             Full path of the node for this entry
389         """
390         return self._node.path
391
392     def GetData(self):
393         return self.data
394
395     def GetOffsets(self):
396         """Get the offsets for siblings
397
398         Some entry types can contain information about the position or size of
399         other entries. An example of this is the Intel Flash Descriptor, which
400         knows where the Intel Management Engine section should go.
401
402         If this entry knows about the position of other entries, it can specify
403         this by returning values here
404
405         Returns:
406             Dict:
407                 key: Entry type
408                 value: List containing position and size of the given entry
409                     type. Either can be None if not known
410         """
411         return {}
412
413     def SetOffsetSize(self, offset, size):
414         """Set the offset and/or size of an entry
415
416         Args:
417             offset: New offset, or None to leave alone
418             size: New size, or None to leave alone
419         """
420         if offset is not None:
421             self.offset = offset
422         if size is not None:
423             self.size = size
424
425     def SetImagePos(self, image_pos):
426         """Set the position in the image
427
428         Args:
429             image_pos: Position of this entry in the image
430         """
431         self.image_pos = image_pos + self.offset
432
433     def ProcessContents(self):
434         """Do any post-packing updates of entry contents
435
436         This function should call ProcessContentsUpdate() to update the entry
437         contents, if necessary, returning its return value here.
438
439         Args:
440             data: Data to set to the contents (bytes)
441
442         Returns:
443             True if the new data size is OK, False if expansion is needed
444
445         Raises:
446             ValueError if the new data size is not the same as the old and
447                 state.AllowEntryExpansion() is False
448         """
449         return True
450
451     def WriteSymbols(self, section):
452         """Write symbol values into binary files for access at run time
453
454         Args:
455           section: Section containing the entry
456         """
457         pass
458
459     def CheckOffset(self):
460         """Check that the entry offsets are correct
461
462         This is used for entries which have extra offset requirements (other
463         than having to be fully inside their section). Sub-classes can implement
464         this function and raise if there is a problem.
465         """
466         pass
467
468     @staticmethod
469     def GetStr(value):
470         if value is None:
471             return '<none>  '
472         return '%08x' % value
473
474     @staticmethod
475     def WriteMapLine(fd, indent, name, offset, size, image_pos):
476         print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
477                                     Entry.GetStr(offset), Entry.GetStr(size),
478                                     name), file=fd)
479
480     def WriteMap(self, fd, indent):
481         """Write a map of the entry to a .map file
482
483         Args:
484             fd: File to write the map to
485             indent: Curent indent level of map (0=none, 1=one level, etc.)
486         """
487         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
488                           self.image_pos)
489
490     def GetEntries(self):
491         """Return a list of entries contained by this entry
492
493         Returns:
494             List of entries, or None if none. A normal entry has no entries
495                 within it so will return None
496         """
497         return None
498
499     def GetArg(self, name, datatype=str):
500         """Get the value of an entry argument or device-tree-node property
501
502         Some node properties can be provided as arguments to binman. First check
503         the entry arguments, and fall back to the device tree if not found
504
505         Args:
506             name: Argument name
507             datatype: Data type (str or int)
508
509         Returns:
510             Value of argument as a string or int, or None if no value
511
512         Raises:
513             ValueError if the argument cannot be converted to in
514         """
515         value = state.GetEntryArg(name)
516         if value is not None:
517             if datatype == int:
518                 try:
519                     value = int(value)
520                 except ValueError:
521                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
522                                (name, value))
523             elif datatype == str:
524                 pass
525             else:
526                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
527                                  datatype)
528         else:
529             value = fdt_util.GetDatatype(self._node, name, datatype)
530         return value
531
532     @staticmethod
533     def WriteDocs(modules, test_missing=None):
534         """Write out documentation about the various entry types to stdout
535
536         Args:
537             modules: List of modules to include
538             test_missing: Used for testing. This is a module to report
539                 as missing
540         """
541         print('''Binman Entry Documentation
542 ===========================
543
544 This file describes the entry types supported by binman. These entry types can
545 be placed in an image one by one to build up a final firmware image. It is
546 fairly easy to create new entry types. Just add a new file to the 'etype'
547 directory. You can use the existing entries as examples.
548
549 Note that some entries are subclasses of others, using and extending their
550 features to produce new behaviours.
551
552
553 ''')
554         modules = sorted(modules)
555
556         # Don't show the test entry
557         if '_testing' in modules:
558             modules.remove('_testing')
559         missing = []
560         for name in modules:
561             if name.startswith('__'):
562                 continue
563             module = Entry.Lookup(name, name)
564             docs = getattr(module, '__doc__')
565             if test_missing == name:
566                 docs = None
567             if docs:
568                 lines = docs.splitlines()
569                 first_line = lines[0]
570                 rest = [line[4:] for line in lines[1:]]
571                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
572                 print(hdr)
573                 print('-' * len(hdr))
574                 print('\n'.join(rest))
575                 print()
576                 print()
577             else:
578                 missing.append(name)
579
580         if missing:
581             raise ValueError('Documentation is missing for modules: %s' %
582                              ', '.join(missing))
583
584     def GetUniqueName(self):
585         """Get a unique name for a node
586
587         Returns:
588             String containing a unique name for a node, consisting of the name
589             of all ancestors (starting from within the 'binman' node) separated
590             by a dot ('.'). This can be useful for generating unique filesnames
591             in the output directory.
592         """
593         name = self.name
594         node = self._node
595         while node.parent:
596             node = node.parent
597             if node.name == 'binman':
598                 break
599             name = '%s.%s' % (node.name, name)
600         return name
601
602     def ExpandToLimit(self, limit):
603         """Expand an entry so that it ends at the given offset limit"""
604         if self.offset + self.size < limit:
605             self.size = limit - self.offset
606             # Request the contents again, since changing the size requires that
607             # the data grows. This should not fail, but check it to be sure.
608             if not self.ObtainContents():
609                 self.Raise('Cannot obtain contents when expanding entry')
610
611     def HasSibling(self, name):
612         """Check if there is a sibling of a given name
613
614         Returns:
615             True if there is an entry with this name in the the same section,
616                 else False
617         """
618         return name in self.section.GetEntries()
619
620     def GetSiblingImagePos(self, name):
621         """Return the image position of the given sibling
622
623         Returns:
624             Image position of sibling, or None if the sibling has no position,
625                 or False if there is no such sibling
626         """
627         if not self.HasSibling(name):
628             return False
629         return self.section.GetEntries()[name].image_pos
630
631     @staticmethod
632     def AddEntryInfo(entries, indent, name, etype, size, image_pos,
633                      uncomp_size, offset, entry):
634         """Add a new entry to the entries list
635
636         Args:
637             entries: List (of EntryInfo objects) to add to
638             indent: Current indent level to add to list
639             name: Entry name (string)
640             etype: Entry type (string)
641             size: Entry size in bytes (int)
642             image_pos: Position within image in bytes (int)
643             uncomp_size: Uncompressed size if the entry uses compression, else
644                 None
645             offset: Entry offset within parent in bytes (int)
646             entry: Entry object
647         """
648         entries.append(EntryInfo(indent, name, etype, size, image_pos,
649                                  uncomp_size, offset, entry))
650
651     def ListEntries(self, entries, indent):
652         """Add files in this entry to the list of entries
653
654         This can be overridden by subclasses which need different behaviour.
655
656         Args:
657             entries: List (of EntryInfo objects) to add to
658             indent: Current indent level to add to list
659         """
660         self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
661                           self.image_pos, self.uncomp_size, self.offset, self)
662
663     def ReadData(self, decomp=True):
664         """Read the data for an entry from the image
665
666         This is used when the image has been read in and we want to extract the
667         data for a particular entry from that image.
668
669         Args:
670             decomp: True to decompress any compressed data before returning it;
671                 False to return the raw, uncompressed data
672
673         Returns:
674             Entry data (bytes)
675         """
676         # Use True here so that we get an uncompressed section to work from,
677         # although compressed sections are currently not supported
678         data = self.section.ReadData(True)
679         tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
680                   (self.GetPath(), self.offset, self.offset + self.size,
681                    self.size, len(data)))
682         return data[self.offset:self.offset + self.size]