binman: Rename state.GetFdts()
[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 GetFdts(self):
189         """Get the device trees used by this entry
190
191         Returns:
192             Empty dict, if this entry is not a .dtb, otherwise:
193             Dict:
194                 key: Filename from this entry (without the path)
195                 value: Fdt object for this dtb, or None if not available
196         """
197         return {}
198
199     def ExpandEntries(self):
200         pass
201
202     def AddMissingProperties(self):
203         """Add new properties to the device tree as needed for this entry"""
204         for prop in ['offset', 'size', 'image-pos']:
205             if not prop in self._node.props:
206                 state.AddZeroProp(self._node, prop)
207         if self.compress != 'none':
208             state.AddZeroProp(self._node, 'uncomp-size')
209         err = state.CheckAddHashProp(self._node)
210         if err:
211             self.Raise(err)
212
213     def SetCalculatedProperties(self):
214         """Set the value of device-tree properties calculated by binman"""
215         state.SetInt(self._node, 'offset', self.offset)
216         state.SetInt(self._node, 'size', self.size)
217         base = self.section.GetRootSkipAtStart() if self.section else 0
218         state.SetInt(self._node, 'image-pos', self.image_pos - base)
219         if self.uncomp_size is not None:
220             state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
221         state.CheckSetHashValue(self._node, self.GetData)
222
223     def ProcessFdt(self, fdt):
224         """Allow entries to adjust the device tree
225
226         Some entries need to adjust the device tree for their purposes. This
227         may involve adding or deleting properties.
228
229         Returns:
230             True if processing is complete
231             False if processing could not be completed due to a dependency.
232                 This will cause the entry to be retried after others have been
233                 called
234         """
235         return True
236
237     def SetPrefix(self, prefix):
238         """Set the name prefix for a node
239
240         Args:
241             prefix: Prefix to set, or '' to not use a prefix
242         """
243         if prefix:
244             self.name = prefix + self.name
245
246     def SetContents(self, data):
247         """Set the contents of an entry
248
249         This sets both the data and content_size properties
250
251         Args:
252             data: Data to set to the contents (bytes)
253         """
254         self.data = data
255         self.contents_size = len(self.data)
256
257     def ProcessContentsUpdate(self, data):
258         """Update the contents of an entry, after the size is fixed
259
260         This checks that the new data is the same size as the old. If the size
261         has changed, this triggers a re-run of the packing algorithm.
262
263         Args:
264             data: Data to set to the contents (bytes)
265
266         Raises:
267             ValueError if the new data size is not the same as the old
268         """
269         size_ok = True
270         new_size = len(data)
271         if state.AllowEntryExpansion():
272             if new_size > self.contents_size:
273                 tout.Debug("Entry '%s' size change from %#x to %#x" % (
274                     self._node.path, self.contents_size, new_size))
275                 # self.data will indicate the new size needed
276                 size_ok = False
277         elif new_size != self.contents_size:
278             self.Raise('Cannot update entry size from %d to %d' %
279                        (self.contents_size, new_size))
280         self.SetContents(data)
281         return size_ok
282
283     def ObtainContents(self):
284         """Figure out the contents of an entry.
285
286         Returns:
287             True if the contents were found, False if another call is needed
288             after the other entries are processed.
289         """
290         # No contents by default: subclasses can implement this
291         return True
292
293     def ResetForPack(self):
294         """Reset offset/size fields so that packing can be done again"""
295         self.offset = self.orig_offset
296         self.size = self.orig_size
297
298     def Pack(self, offset):
299         """Figure out how to pack the entry into the section
300
301         Most of the time the entries are not fully specified. There may be
302         an alignment but no size. In that case we take the size from the
303         contents of the entry.
304
305         If an entry has no hard-coded offset, it will be placed at @offset.
306
307         Once this function is complete, both the offset and size of the
308         entry will be know.
309
310         Args:
311             Current section offset pointer
312
313         Returns:
314             New section offset pointer (after this entry)
315         """
316         if self.offset is None:
317             if self.offset_unset:
318                 self.Raise('No offset set with offset-unset: should another '
319                            'entry provide this correct offset?')
320             self.offset = tools.Align(offset, self.align)
321         needed = self.pad_before + self.contents_size + self.pad_after
322         needed = tools.Align(needed, self.align_size)
323         size = self.size
324         if not size:
325             size = needed
326         new_offset = self.offset + size
327         aligned_offset = tools.Align(new_offset, self.align_end)
328         if aligned_offset != new_offset:
329             size = aligned_offset - self.offset
330             new_offset = aligned_offset
331
332         if not self.size:
333             self.size = size
334
335         if self.size < needed:
336             self.Raise("Entry contents size is %#x (%d) but entry size is "
337                        "%#x (%d)" % (needed, needed, self.size, self.size))
338         # Check that the alignment is correct. It could be wrong if the
339         # and offset or size values were provided (i.e. not calculated), but
340         # conflict with the provided alignment values
341         if self.size != tools.Align(self.size, self.align_size):
342             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
343                   (self.size, self.size, self.align_size, self.align_size))
344         if self.offset != tools.Align(self.offset, self.align):
345             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
346                   (self.offset, self.offset, self.align, self.align))
347
348         return new_offset
349
350     def Raise(self, msg):
351         """Convenience function to raise an error referencing a node"""
352         raise ValueError("Node '%s': %s" % (self._node.path, msg))
353
354     def GetEntryArgsOrProps(self, props, required=False):
355         """Return the values of a set of properties
356
357         Args:
358             props: List of EntryArg objects
359
360         Raises:
361             ValueError if a property is not found
362         """
363         values = []
364         missing = []
365         for prop in props:
366             python_prop = prop.name.replace('-', '_')
367             if hasattr(self, python_prop):
368                 value = getattr(self, python_prop)
369             else:
370                 value = None
371             if value is None:
372                 value = self.GetArg(prop.name, prop.datatype)
373             if value is None and required:
374                 missing.append(prop.name)
375             values.append(value)
376         if missing:
377             self.Raise('Missing required properties/entry args: %s' %
378                        (', '.join(missing)))
379         return values
380
381     def GetPath(self):
382         """Get the path of a node
383
384         Returns:
385             Full path of the node for this entry
386         """
387         return self._node.path
388
389     def GetData(self):
390         return self.data
391
392     def GetOffsets(self):
393         """Get the offsets for siblings
394
395         Some entry types can contain information about the position or size of
396         other entries. An example of this is the Intel Flash Descriptor, which
397         knows where the Intel Management Engine section should go.
398
399         If this entry knows about the position of other entries, it can specify
400         this by returning values here
401
402         Returns:
403             Dict:
404                 key: Entry type
405                 value: List containing position and size of the given entry
406                     type. Either can be None if not known
407         """
408         return {}
409
410     def SetOffsetSize(self, offset, size):
411         """Set the offset and/or size of an entry
412
413         Args:
414             offset: New offset, or None to leave alone
415             size: New size, or None to leave alone
416         """
417         if offset is not None:
418             self.offset = offset
419         if size is not None:
420             self.size = size
421
422     def SetImagePos(self, image_pos):
423         """Set the position in the image
424
425         Args:
426             image_pos: Position of this entry in the image
427         """
428         self.image_pos = image_pos + self.offset
429
430     def ProcessContents(self):
431         """Do any post-packing updates of entry contents
432
433         This function should call ProcessContentsUpdate() to update the entry
434         contents, if necessary, returning its return value here.
435
436         Args:
437             data: Data to set to the contents (bytes)
438
439         Returns:
440             True if the new data size is OK, False if expansion is needed
441
442         Raises:
443             ValueError if the new data size is not the same as the old and
444                 state.AllowEntryExpansion() is False
445         """
446         return True
447
448     def WriteSymbols(self, section):
449         """Write symbol values into binary files for access at run time
450
451         Args:
452           section: Section containing the entry
453         """
454         pass
455
456     def CheckOffset(self):
457         """Check that the entry offsets are correct
458
459         This is used for entries which have extra offset requirements (other
460         than having to be fully inside their section). Sub-classes can implement
461         this function and raise if there is a problem.
462         """
463         pass
464
465     @staticmethod
466     def GetStr(value):
467         if value is None:
468             return '<none>  '
469         return '%08x' % value
470
471     @staticmethod
472     def WriteMapLine(fd, indent, name, offset, size, image_pos):
473         print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
474                                     Entry.GetStr(offset), Entry.GetStr(size),
475                                     name), file=fd)
476
477     def WriteMap(self, fd, indent):
478         """Write a map of the entry to a .map file
479
480         Args:
481             fd: File to write the map to
482             indent: Curent indent level of map (0=none, 1=one level, etc.)
483         """
484         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
485                           self.image_pos)
486
487     def GetEntries(self):
488         """Return a list of entries contained by this entry
489
490         Returns:
491             List of entries, or None if none. A normal entry has no entries
492                 within it so will return None
493         """
494         return None
495
496     def GetArg(self, name, datatype=str):
497         """Get the value of an entry argument or device-tree-node property
498
499         Some node properties can be provided as arguments to binman. First check
500         the entry arguments, and fall back to the device tree if not found
501
502         Args:
503             name: Argument name
504             datatype: Data type (str or int)
505
506         Returns:
507             Value of argument as a string or int, or None if no value
508
509         Raises:
510             ValueError if the argument cannot be converted to in
511         """
512         value = state.GetEntryArg(name)
513         if value is not None:
514             if datatype == int:
515                 try:
516                     value = int(value)
517                 except ValueError:
518                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
519                                (name, value))
520             elif datatype == str:
521                 pass
522             else:
523                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
524                                  datatype)
525         else:
526             value = fdt_util.GetDatatype(self._node, name, datatype)
527         return value
528
529     @staticmethod
530     def WriteDocs(modules, test_missing=None):
531         """Write out documentation about the various entry types to stdout
532
533         Args:
534             modules: List of modules to include
535             test_missing: Used for testing. This is a module to report
536                 as missing
537         """
538         print('''Binman Entry Documentation
539 ===========================
540
541 This file describes the entry types supported by binman. These entry types can
542 be placed in an image one by one to build up a final firmware image. It is
543 fairly easy to create new entry types. Just add a new file to the 'etype'
544 directory. You can use the existing entries as examples.
545
546 Note that some entries are subclasses of others, using and extending their
547 features to produce new behaviours.
548
549
550 ''')
551         modules = sorted(modules)
552
553         # Don't show the test entry
554         if '_testing' in modules:
555             modules.remove('_testing')
556         missing = []
557         for name in modules:
558             if name.startswith('__'):
559                 continue
560             module = Entry.Lookup(name, name)
561             docs = getattr(module, '__doc__')
562             if test_missing == name:
563                 docs = None
564             if docs:
565                 lines = docs.splitlines()
566                 first_line = lines[0]
567                 rest = [line[4:] for line in lines[1:]]
568                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
569                 print(hdr)
570                 print('-' * len(hdr))
571                 print('\n'.join(rest))
572                 print()
573                 print()
574             else:
575                 missing.append(name)
576
577         if missing:
578             raise ValueError('Documentation is missing for modules: %s' %
579                              ', '.join(missing))
580
581     def GetUniqueName(self):
582         """Get a unique name for a node
583
584         Returns:
585             String containing a unique name for a node, consisting of the name
586             of all ancestors (starting from within the 'binman' node) separated
587             by a dot ('.'). This can be useful for generating unique filesnames
588             in the output directory.
589         """
590         name = self.name
591         node = self._node
592         while node.parent:
593             node = node.parent
594             if node.name == 'binman':
595                 break
596             name = '%s.%s' % (node.name, name)
597         return name
598
599     def ExpandToLimit(self, limit):
600         """Expand an entry so that it ends at the given offset limit"""
601         if self.offset + self.size < limit:
602             self.size = limit - self.offset
603             # Request the contents again, since changing the size requires that
604             # the data grows. This should not fail, but check it to be sure.
605             if not self.ObtainContents():
606                 self.Raise('Cannot obtain contents when expanding entry')
607
608     def HasSibling(self, name):
609         """Check if there is a sibling of a given name
610
611         Returns:
612             True if there is an entry with this name in the the same section,
613                 else False
614         """
615         return name in self.section.GetEntries()
616
617     def GetSiblingImagePos(self, name):
618         """Return the image position of the given sibling
619
620         Returns:
621             Image position of sibling, or None if the sibling has no position,
622                 or False if there is no such sibling
623         """
624         if not self.HasSibling(name):
625             return False
626         return self.section.GetEntries()[name].image_pos
627
628     @staticmethod
629     def AddEntryInfo(entries, indent, name, etype, size, image_pos,
630                      uncomp_size, offset, entry):
631         """Add a new entry to the entries list
632
633         Args:
634             entries: List (of EntryInfo objects) to add to
635             indent: Current indent level to add to list
636             name: Entry name (string)
637             etype: Entry type (string)
638             size: Entry size in bytes (int)
639             image_pos: Position within image in bytes (int)
640             uncomp_size: Uncompressed size if the entry uses compression, else
641                 None
642             offset: Entry offset within parent in bytes (int)
643             entry: Entry object
644         """
645         entries.append(EntryInfo(indent, name, etype, size, image_pos,
646                                  uncomp_size, offset, entry))
647
648     def ListEntries(self, entries, indent):
649         """Add files in this entry to the list of entries
650
651         This can be overridden by subclasses which need different behaviour.
652
653         Args:
654             entries: List (of EntryInfo objects) to add to
655             indent: Current indent level to add to list
656         """
657         self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
658                           self.image_pos, self.uncomp_size, self.offset, self)
659
660     def ReadData(self, decomp=True):
661         """Read the data for an entry from the image
662
663         This is used when the image has been read in and we want to extract the
664         data for a particular entry from that image.
665
666         Args:
667             decomp: True to decompress any compressed data before returning it;
668                 False to return the raw, uncompressed data
669
670         Returns:
671             Entry data (bytes)
672         """
673         # Use True here so that we get an uncompressed section to work from,
674         # although compressed sections are currently not supported
675         data = self.section.ReadData(True)
676         tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
677                   (self.GetPath(), self.offset, self.offset + self.size,
678                    self.size, len(data)))
679         return data[self.offset:self.offset + self.size]