binman: Update documentation for image creation
[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():
289             if new_size > self.contents_size:
290                 tout.Debug("Entry '%s' size change from %s to %s" % (
291                     self._node.path, ToHex(self.contents_size),
292                     ToHex(new_size)))
293                 # self.data will indicate the new size needed
294                 size_ok = False
295         elif new_size != self.contents_size:
296             self.Raise('Cannot update entry size from %d to %d' %
297                        (self.contents_size, new_size))
298         self.SetContents(data)
299         return size_ok
300
301     def ObtainContents(self):
302         """Figure out the contents of an entry.
303
304         Returns:
305             True if the contents were found, False if another call is needed
306             after the other entries are processed.
307         """
308         # No contents by default: subclasses can implement this
309         return True
310
311     def ResetForPack(self):
312         """Reset offset/size fields so that packing can be done again"""
313         self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
314                     (ToHex(self.offset), ToHex(self.orig_offset),
315                      ToHex(self.size), ToHex(self.orig_size)))
316         self.offset = self.orig_offset
317         self.size = self.orig_size
318
319     def Pack(self, offset):
320         """Figure out how to pack the entry into the section
321
322         Most of the time the entries are not fully specified. There may be
323         an alignment but no size. In that case we take the size from the
324         contents of the entry.
325
326         If an entry has no hard-coded offset, it will be placed at @offset.
327
328         Once this function is complete, both the offset and size of the
329         entry will be know.
330
331         Args:
332             Current section offset pointer
333
334         Returns:
335             New section offset pointer (after this entry)
336         """
337         self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
338                     (ToHex(self.offset), ToHex(self.size),
339                      self.contents_size))
340         if self.offset is None:
341             if self.offset_unset:
342                 self.Raise('No offset set with offset-unset: should another '
343                            'entry provide this correct offset?')
344             self.offset = tools.Align(offset, self.align)
345         needed = self.pad_before + self.contents_size + self.pad_after
346         needed = tools.Align(needed, self.align_size)
347         size = self.size
348         if not size:
349             size = needed
350         new_offset = self.offset + size
351         aligned_offset = tools.Align(new_offset, self.align_end)
352         if aligned_offset != new_offset:
353             size = aligned_offset - self.offset
354             new_offset = aligned_offset
355
356         if not self.size:
357             self.size = size
358
359         if self.size < needed:
360             self.Raise("Entry contents size is %#x (%d) but entry size is "
361                        "%#x (%d)" % (needed, needed, self.size, self.size))
362         # Check that the alignment is correct. It could be wrong if the
363         # and offset or size values were provided (i.e. not calculated), but
364         # conflict with the provided alignment values
365         if self.size != tools.Align(self.size, self.align_size):
366             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
367                   (self.size, self.size, self.align_size, self.align_size))
368         if self.offset != tools.Align(self.offset, self.align):
369             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
370                   (self.offset, self.offset, self.align, self.align))
371         self.Detail('   - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
372                     (self.offset, self.size, self.contents_size, new_offset))
373
374         return new_offset
375
376     def Raise(self, msg):
377         """Convenience function to raise an error referencing a node"""
378         raise ValueError("Node '%s': %s" % (self._node.path, msg))
379
380     def Detail(self, msg):
381         """Convenience function to log detail referencing a node"""
382         tag = "Node '%s'" % self._node.path
383         tout.Detail('%30s: %s' % (tag, msg))
384
385     def GetEntryArgsOrProps(self, props, required=False):
386         """Return the values of a set of properties
387
388         Args:
389             props: List of EntryArg objects
390
391         Raises:
392             ValueError if a property is not found
393         """
394         values = []
395         missing = []
396         for prop in props:
397             python_prop = prop.name.replace('-', '_')
398             if hasattr(self, python_prop):
399                 value = getattr(self, python_prop)
400             else:
401                 value = None
402             if value is None:
403                 value = self.GetArg(prop.name, prop.datatype)
404             if value is None and required:
405                 missing.append(prop.name)
406             values.append(value)
407         if missing:
408             self.Raise('Missing required properties/entry args: %s' %
409                        (', '.join(missing)))
410         return values
411
412     def GetPath(self):
413         """Get the path of a node
414
415         Returns:
416             Full path of the node for this entry
417         """
418         return self._node.path
419
420     def GetData(self):
421         self.Detail('GetData: size %s' % ToHexSize(self.data))
422         return self.data
423
424     def GetOffsets(self):
425         """Get the offsets for siblings
426
427         Some entry types can contain information about the position or size of
428         other entries. An example of this is the Intel Flash Descriptor, which
429         knows where the Intel Management Engine section should go.
430
431         If this entry knows about the position of other entries, it can specify
432         this by returning values here
433
434         Returns:
435             Dict:
436                 key: Entry type
437                 value: List containing position and size of the given entry
438                     type. Either can be None if not known
439         """
440         return {}
441
442     def SetOffsetSize(self, offset, size):
443         """Set the offset and/or size of an entry
444
445         Args:
446             offset: New offset, or None to leave alone
447             size: New size, or None to leave alone
448         """
449         if offset is not None:
450             self.offset = offset
451         if size is not None:
452             self.size = size
453
454     def SetImagePos(self, image_pos):
455         """Set the position in the image
456
457         Args:
458             image_pos: Position of this entry in the image
459         """
460         self.image_pos = image_pos + self.offset
461
462     def ProcessContents(self):
463         """Do any post-packing updates of entry contents
464
465         This function should call ProcessContentsUpdate() to update the entry
466         contents, if necessary, returning its return value here.
467
468         Args:
469             data: Data to set to the contents (bytes)
470
471         Returns:
472             True if the new data size is OK, False if expansion is needed
473
474         Raises:
475             ValueError if the new data size is not the same as the old and
476                 state.AllowEntryExpansion() is False
477         """
478         return True
479
480     def WriteSymbols(self, section):
481         """Write symbol values into binary files for access at run time
482
483         Args:
484           section: Section containing the entry
485         """
486         pass
487
488     def CheckOffset(self):
489         """Check that the entry offsets are correct
490
491         This is used for entries which have extra offset requirements (other
492         than having to be fully inside their section). Sub-classes can implement
493         this function and raise if there is a problem.
494         """
495         pass
496
497     @staticmethod
498     def GetStr(value):
499         if value is None:
500             return '<none>  '
501         return '%08x' % value
502
503     @staticmethod
504     def WriteMapLine(fd, indent, name, offset, size, image_pos):
505         print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
506                                     Entry.GetStr(offset), Entry.GetStr(size),
507                                     name), file=fd)
508
509     def WriteMap(self, fd, indent):
510         """Write a map of the entry to a .map file
511
512         Args:
513             fd: File to write the map to
514             indent: Curent indent level of map (0=none, 1=one level, etc.)
515         """
516         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
517                           self.image_pos)
518
519     def GetEntries(self):
520         """Return a list of entries contained by this entry
521
522         Returns:
523             List of entries, or None if none. A normal entry has no entries
524                 within it so will return None
525         """
526         return None
527
528     def GetArg(self, name, datatype=str):
529         """Get the value of an entry argument or device-tree-node property
530
531         Some node properties can be provided as arguments to binman. First check
532         the entry arguments, and fall back to the device tree if not found
533
534         Args:
535             name: Argument name
536             datatype: Data type (str or int)
537
538         Returns:
539             Value of argument as a string or int, or None if no value
540
541         Raises:
542             ValueError if the argument cannot be converted to in
543         """
544         value = state.GetEntryArg(name)
545         if value is not None:
546             if datatype == int:
547                 try:
548                     value = int(value)
549                 except ValueError:
550                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
551                                (name, value))
552             elif datatype == str:
553                 pass
554             else:
555                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
556                                  datatype)
557         else:
558             value = fdt_util.GetDatatype(self._node, name, datatype)
559         return value
560
561     @staticmethod
562     def WriteDocs(modules, test_missing=None):
563         """Write out documentation about the various entry types to stdout
564
565         Args:
566             modules: List of modules to include
567             test_missing: Used for testing. This is a module to report
568                 as missing
569         """
570         print('''Binman Entry Documentation
571 ===========================
572
573 This file describes the entry types supported by binman. These entry types can
574 be placed in an image one by one to build up a final firmware image. It is
575 fairly easy to create new entry types. Just add a new file to the 'etype'
576 directory. You can use the existing entries as examples.
577
578 Note that some entries are subclasses of others, using and extending their
579 features to produce new behaviours.
580
581
582 ''')
583         modules = sorted(modules)
584
585         # Don't show the test entry
586         if '_testing' in modules:
587             modules.remove('_testing')
588         missing = []
589         for name in modules:
590             if name.startswith('__'):
591                 continue
592             module = Entry.Lookup(name, name)
593             docs = getattr(module, '__doc__')
594             if test_missing == name:
595                 docs = None
596             if docs:
597                 lines = docs.splitlines()
598                 first_line = lines[0]
599                 rest = [line[4:] for line in lines[1:]]
600                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
601                 print(hdr)
602                 print('-' * len(hdr))
603                 print('\n'.join(rest))
604                 print()
605                 print()
606             else:
607                 missing.append(name)
608
609         if missing:
610             raise ValueError('Documentation is missing for modules: %s' %
611                              ', '.join(missing))
612
613     def GetUniqueName(self):
614         """Get a unique name for a node
615
616         Returns:
617             String containing a unique name for a node, consisting of the name
618             of all ancestors (starting from within the 'binman' node) separated
619             by a dot ('.'). This can be useful for generating unique filesnames
620             in the output directory.
621         """
622         name = self.name
623         node = self._node
624         while node.parent:
625             node = node.parent
626             if node.name == 'binman':
627                 break
628             name = '%s.%s' % (node.name, name)
629         return name
630
631     def ExpandToLimit(self, limit):
632         """Expand an entry so that it ends at the given offset limit"""
633         if self.offset + self.size < limit:
634             self.size = limit - self.offset
635             # Request the contents again, since changing the size requires that
636             # the data grows. This should not fail, but check it to be sure.
637             if not self.ObtainContents():
638                 self.Raise('Cannot obtain contents when expanding entry')
639
640     def HasSibling(self, name):
641         """Check if there is a sibling of a given name
642
643         Returns:
644             True if there is an entry with this name in the the same section,
645                 else False
646         """
647         return name in self.section.GetEntries()
648
649     def GetSiblingImagePos(self, name):
650         """Return the image position of the given sibling
651
652         Returns:
653             Image position of sibling, or None if the sibling has no position,
654                 or False if there is no such sibling
655         """
656         if not self.HasSibling(name):
657             return False
658         return self.section.GetEntries()[name].image_pos
659
660     @staticmethod
661     def AddEntryInfo(entries, indent, name, etype, size, image_pos,
662                      uncomp_size, offset, entry):
663         """Add a new entry to the entries list
664
665         Args:
666             entries: List (of EntryInfo objects) to add to
667             indent: Current indent level to add to list
668             name: Entry name (string)
669             etype: Entry type (string)
670             size: Entry size in bytes (int)
671             image_pos: Position within image in bytes (int)
672             uncomp_size: Uncompressed size if the entry uses compression, else
673                 None
674             offset: Entry offset within parent in bytes (int)
675             entry: Entry object
676         """
677         entries.append(EntryInfo(indent, name, etype, size, image_pos,
678                                  uncomp_size, offset, entry))
679
680     def ListEntries(self, entries, indent):
681         """Add files in this entry to the list of entries
682
683         This can be overridden by subclasses which need different behaviour.
684
685         Args:
686             entries: List (of EntryInfo objects) to add to
687             indent: Current indent level to add to list
688         """
689         self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
690                           self.image_pos, self.uncomp_size, self.offset, self)
691
692     def ReadData(self, decomp=True):
693         """Read the data for an entry from the image
694
695         This is used when the image has been read in and we want to extract the
696         data for a particular entry from that image.
697
698         Args:
699             decomp: True to decompress any compressed data before returning it;
700                 False to return the raw, uncompressed data
701
702         Returns:
703             Entry data (bytes)
704         """
705         # Use True here so that we get an uncompressed section to work from,
706         # although compressed sections are currently not supported
707         data = self.section.ReadData(True)
708         tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
709                   (self.GetPath(), self.offset, self.offset + self.size,
710                    self.size, len(data)))
711         return data[self.offset:self.offset + self.size]
712
713     def LoadData(self, decomp=True):
714         data = self.ReadData(decomp)
715         self.contents_size = len(data)
716         self.ProcessContentsUpdate(data)
717         self.Detail('Loaded data size %x' % len(data))
718
719     def GetImage(self):
720         """Get the image containing this entry
721
722         Returns:
723             Image object containing this entry
724         """
725         return self.section.GetImage()
726
727     def WriteData(self, data, decomp=True):
728         """Write the data to an entry in the image
729
730         This is used when the image has been read in and we want to replace the
731         data for a particular entry in that image.
732
733         The image must be re-packed and written out afterwards.
734
735         Args:
736             data: Data to replace it with
737             decomp: True to compress the data if needed, False if data is
738                 already compressed so should be used as is
739
740         Returns:
741             True if the data did not result in a resize of this entry, False if
742                  the entry must be resized
743         """
744         self.contents_size = self.size
745         ok = self.ProcessContentsUpdate(data)
746         self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
747         return ok