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