binman: Correct fmap output on x86
[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 from sets import Set
22 import sys
23
24 import fdt_util
25 import state
26 import tools
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
38 class Entry(object):
39     """An Entry in the section
40
41     An entry corresponds to a single node in the device-tree description
42     of the section. Each entry ends up being a part of the final section.
43     Entries can be placed either right next to each other, or with padding
44     between them. The type of the entry determines the data that is in it.
45
46     This class is not used by itself. All entry objects are subclasses of
47     Entry.
48
49     Attributes:
50         section: Section object containing this entry
51         node: The node that created this entry
52         offset: Offset of entry within the section, None if not known yet (in
53             which case it will be calculated by Pack())
54         size: Entry size in bytes, None if not known
55         contents_size: Size of contents in bytes, 0 by default
56         align: Entry start offset alignment, or None
57         align_size: Entry size alignment, or None
58         align_end: Entry end offset alignment, or None
59         pad_before: Number of pad bytes before the contents, 0 if none
60         pad_after: Number of pad bytes after the contents, 0 if none
61         data: Contents of entry (string of bytes)
62     """
63     def __init__(self, section, etype, node, read_node=True, name_prefix=''):
64         self.section = section
65         self.etype = etype
66         self._node = node
67         self.name = node and (name_prefix + node.name) or 'none'
68         self.offset = None
69         self.size = None
70         self.data = None
71         self.contents_size = 0
72         self.align = None
73         self.align_size = None
74         self.align_end = None
75         self.pad_before = 0
76         self.pad_after = 0
77         self.offset_unset = False
78         self.image_pos = None
79         self._expand_size = False
80         if read_node:
81             self.ReadNode()
82
83     @staticmethod
84     def Lookup(section, node_path, etype):
85         """Look up the entry class for a node.
86
87         Args:
88             section:   Section object containing this node
89             node_node: Path name of Node object containing information about
90                        the entry to create (used for errors)
91             etype:   Entry type to use
92
93         Returns:
94             The entry class object if found, else None
95         """
96         # Convert something like 'u-boot@0' to 'u_boot' since we are only
97         # interested in the type.
98         module_name = etype.replace('-', '_')
99         if '@' in module_name:
100             module_name = module_name.split('@')[0]
101         module = modules.get(module_name)
102
103         # Also allow entry-type modules to be brought in from the etype directory.
104
105         # Import the module if we have not already done so.
106         if not module:
107             old_path = sys.path
108             sys.path.insert(0, os.path.join(our_path, 'etype'))
109             try:
110                 if have_importlib:
111                     module = importlib.import_module(module_name)
112                 else:
113                     module = __import__(module_name)
114             except ImportError as e:
115                 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
116                                  (etype, node_path, module_name, e))
117             finally:
118                 sys.path = old_path
119             modules[module_name] = module
120
121         # Look up the expected class name
122         return getattr(module, 'Entry_%s' % module_name)
123
124     @staticmethod
125     def Create(section, node, etype=None):
126         """Create a new entry for a node.
127
128         Args:
129             section: Section object containing this node
130             node:    Node object containing information about the entry to
131                      create
132             etype:   Entry type to use, or None to work it out (used for tests)
133
134         Returns:
135             A new Entry object of the correct type (a subclass of Entry)
136         """
137         if not etype:
138             etype = fdt_util.GetString(node, 'type', node.name)
139         obj = Entry.Lookup(section, node.path, etype)
140
141         # Call its constructor to get the object we want.
142         return obj(section, etype, node)
143
144     def ReadNode(self):
145         """Read entry information from the node
146
147         This reads all the fields we recognise from the node, ready for use.
148         """
149         if 'pos' in self._node.props:
150             self.Raise("Please use 'offset' instead of 'pos'")
151         self.offset = fdt_util.GetInt(self._node, 'offset')
152         self.size = fdt_util.GetInt(self._node, 'size')
153         self.align = fdt_util.GetInt(self._node, 'align')
154         if tools.NotPowerOfTwo(self.align):
155             raise ValueError("Node '%s': Alignment %s must be a power of two" %
156                              (self._node.path, self.align))
157         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
158         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
159         self.align_size = fdt_util.GetInt(self._node, 'align-size')
160         if tools.NotPowerOfTwo(self.align_size):
161             raise ValueError("Node '%s': Alignment size %s must be a power "
162                              "of two" % (self._node.path, self.align_size))
163         self.align_end = fdt_util.GetInt(self._node, 'align-end')
164         self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
165         self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
166
167     def GetDefaultFilename(self):
168         return None
169
170     def GetFdtSet(self):
171         """Get the set of device trees used by this entry
172
173         Returns:
174             Set containing the filename from this entry, if it is a .dtb, else
175             an empty set
176         """
177         fname = self.GetDefaultFilename()
178         # It would be better to use isinstance(self, Entry_blob_dtb) here but
179         # we cannot access Entry_blob_dtb
180         if fname and fname.endswith('.dtb'):
181             return Set([fname])
182         return Set()
183
184     def ExpandEntries(self):
185         pass
186
187     def AddMissingProperties(self):
188         """Add new properties to the device tree as needed for this entry"""
189         for prop in ['offset', 'size', 'image-pos']:
190             if not prop in self._node.props:
191                 state.AddZeroProp(self._node, prop)
192         err = state.CheckAddHashProp(self._node)
193         if err:
194             self.Raise(err)
195
196     def SetCalculatedProperties(self):
197         """Set the value of device-tree properties calculated by binman"""
198         state.SetInt(self._node, 'offset', self.offset)
199         state.SetInt(self._node, 'size', self.size)
200         state.SetInt(self._node, 'image-pos',
201                        self.image_pos - self.section.GetRootSkipAtStart())
202         state.CheckSetHashValue(self._node, self.GetData)
203
204     def ProcessFdt(self, fdt):
205         """Allow entries to adjust the device tree
206
207         Some entries need to adjust the device tree for their purposes. This
208         may involve adding or deleting properties.
209
210         Returns:
211             True if processing is complete
212             False if processing could not be completed due to a dependency.
213                 This will cause the entry to be retried after others have been
214                 called
215         """
216         return True
217
218     def SetPrefix(self, prefix):
219         """Set the name prefix for a node
220
221         Args:
222             prefix: Prefix to set, or '' to not use a prefix
223         """
224         if prefix:
225             self.name = prefix + self.name
226
227     def SetContents(self, data):
228         """Set the contents of an entry
229
230         This sets both the data and content_size properties
231
232         Args:
233             data: Data to set to the contents (string)
234         """
235         self.data = data
236         self.contents_size = len(self.data)
237
238     def ProcessContentsUpdate(self, data):
239         """Update the contens of an entry, after the size is fixed
240
241         This checks that the new data is the same size as the old.
242
243         Args:
244             data: Data to set to the contents (string)
245
246         Raises:
247             ValueError if the new data size is not the same as the old
248         """
249         if len(data) != self.contents_size:
250             self.Raise('Cannot update entry size from %d to %d' %
251                        (len(data), self.contents_size))
252         self.SetContents(data)
253
254     def ObtainContents(self):
255         """Figure out the contents of an entry.
256
257         Returns:
258             True if the contents were found, False if another call is needed
259             after the other entries are processed.
260         """
261         # No contents by default: subclasses can implement this
262         return True
263
264     def Pack(self, offset):
265         """Figure out how to pack the entry into the section
266
267         Most of the time the entries are not fully specified. There may be
268         an alignment but no size. In that case we take the size from the
269         contents of the entry.
270
271         If an entry has no hard-coded offset, it will be placed at @offset.
272
273         Once this function is complete, both the offset and size of the
274         entry will be know.
275
276         Args:
277             Current section offset pointer
278
279         Returns:
280             New section offset pointer (after this entry)
281         """
282         if self.offset is None:
283             if self.offset_unset:
284                 self.Raise('No offset set with offset-unset: should another '
285                            'entry provide this correct offset?')
286             self.offset = tools.Align(offset, self.align)
287         needed = self.pad_before + self.contents_size + self.pad_after
288         needed = tools.Align(needed, self.align_size)
289         size = self.size
290         if not size:
291             size = needed
292         new_offset = self.offset + size
293         aligned_offset = tools.Align(new_offset, self.align_end)
294         if aligned_offset != new_offset:
295             size = aligned_offset - self.offset
296             new_offset = aligned_offset
297
298         if not self.size:
299             self.size = size
300
301         if self.size < needed:
302             self.Raise("Entry contents size is %#x (%d) but entry size is "
303                        "%#x (%d)" % (needed, needed, self.size, self.size))
304         # Check that the alignment is correct. It could be wrong if the
305         # and offset or size values were provided (i.e. not calculated), but
306         # conflict with the provided alignment values
307         if self.size != tools.Align(self.size, self.align_size):
308             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
309                   (self.size, self.size, self.align_size, self.align_size))
310         if self.offset != tools.Align(self.offset, self.align):
311             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
312                   (self.offset, self.offset, self.align, self.align))
313
314         return new_offset
315
316     def Raise(self, msg):
317         """Convenience function to raise an error referencing a node"""
318         raise ValueError("Node '%s': %s" % (self._node.path, msg))
319
320     def GetEntryArgsOrProps(self, props, required=False):
321         """Return the values of a set of properties
322
323         Args:
324             props: List of EntryArg objects
325
326         Raises:
327             ValueError if a property is not found
328         """
329         values = []
330         missing = []
331         for prop in props:
332             python_prop = prop.name.replace('-', '_')
333             if hasattr(self, python_prop):
334                 value = getattr(self, python_prop)
335             else:
336                 value = None
337             if value is None:
338                 value = self.GetArg(prop.name, prop.datatype)
339             if value is None and required:
340                 missing.append(prop.name)
341             values.append(value)
342         if missing:
343             self.Raise('Missing required properties/entry args: %s' %
344                        (', '.join(missing)))
345         return values
346
347     def GetPath(self):
348         """Get the path of a node
349
350         Returns:
351             Full path of the node for this entry
352         """
353         return self._node.path
354
355     def GetData(self):
356         return self.data
357
358     def GetOffsets(self):
359         return {}
360
361     def SetOffsetSize(self, pos, size):
362         self.offset = pos
363         self.size = size
364
365     def SetImagePos(self, image_pos):
366         """Set the position in the image
367
368         Args:
369             image_pos: Position of this entry in the image
370         """
371         self.image_pos = image_pos + self.offset
372
373     def ProcessContents(self):
374         pass
375
376     def WriteSymbols(self, section):
377         """Write symbol values into binary files for access at run time
378
379         Args:
380           section: Section containing the entry
381         """
382         pass
383
384     def CheckOffset(self):
385         """Check that the entry offsets are correct
386
387         This is used for entries which have extra offset requirements (other
388         than having to be fully inside their section). Sub-classes can implement
389         this function and raise if there is a problem.
390         """
391         pass
392
393     @staticmethod
394     def WriteMapLine(fd, indent, name, offset, size, image_pos):
395         print('%08x  %s%08x  %08x  %s' % (image_pos, ' ' * indent, offset,
396                                           size, name), file=fd)
397
398     def WriteMap(self, fd, indent):
399         """Write a map of the entry to a .map file
400
401         Args:
402             fd: File to write the map to
403             indent: Curent indent level of map (0=none, 1=one level, etc.)
404         """
405         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
406                           self.image_pos)
407
408     def GetEntries(self):
409         """Return a list of entries contained by this entry
410
411         Returns:
412             List of entries, or None if none. A normal entry has no entries
413                 within it so will return None
414         """
415         return None
416
417     def GetArg(self, name, datatype=str):
418         """Get the value of an entry argument or device-tree-node property
419
420         Some node properties can be provided as arguments to binman. First check
421         the entry arguments, and fall back to the device tree if not found
422
423         Args:
424             name: Argument name
425             datatype: Data type (str or int)
426
427         Returns:
428             Value of argument as a string or int, or None if no value
429
430         Raises:
431             ValueError if the argument cannot be converted to in
432         """
433         value = state.GetEntryArg(name)
434         if value is not None:
435             if datatype == int:
436                 try:
437                     value = int(value)
438                 except ValueError:
439                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
440                                (name, value))
441             elif datatype == str:
442                 pass
443             else:
444                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
445                                  datatype)
446         else:
447             value = fdt_util.GetDatatype(self._node, name, datatype)
448         return value
449
450     @staticmethod
451     def WriteDocs(modules, test_missing=None):
452         """Write out documentation about the various entry types to stdout
453
454         Args:
455             modules: List of modules to include
456             test_missing: Used for testing. This is a module to report
457                 as missing
458         """
459         print('''Binman Entry Documentation
460 ===========================
461
462 This file describes the entry types supported by binman. These entry types can
463 be placed in an image one by one to build up a final firmware image. It is
464 fairly easy to create new entry types. Just add a new file to the 'etype'
465 directory. You can use the existing entries as examples.
466
467 Note that some entries are subclasses of others, using and extending their
468 features to produce new behaviours.
469
470
471 ''')
472         modules = sorted(modules)
473
474         # Don't show the test entry
475         if '_testing' in modules:
476             modules.remove('_testing')
477         missing = []
478         for name in modules:
479             module = Entry.Lookup(name, name, name)
480             docs = getattr(module, '__doc__')
481             if test_missing == name:
482                 docs = None
483             if docs:
484                 lines = docs.splitlines()
485                 first_line = lines[0]
486                 rest = [line[4:] for line in lines[1:]]
487                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
488                 print(hdr)
489                 print('-' * len(hdr))
490                 print('\n'.join(rest))
491                 print()
492                 print()
493             else:
494                 missing.append(name)
495
496         if missing:
497             raise ValueError('Documentation is missing for modules: %s' %
498                              ', '.join(missing))
499
500     def GetUniqueName(self):
501         """Get a unique name for a node
502
503         Returns:
504             String containing a unique name for a node, consisting of the name
505             of all ancestors (starting from within the 'binman' node) separated
506             by a dot ('.'). This can be useful for generating unique filesnames
507             in the output directory.
508         """
509         name = self.name
510         node = self._node
511         while node.parent:
512             node = node.parent
513             if node.name == 'binman':
514                 break
515             name = '%s.%s' % (node.name, name)
516         return name
517
518     def ExpandToLimit(self, limit):
519         """Expand an entry so that it ends at the given offset limit"""
520         if self.offset + self.size < limit:
521             self.size = limit - self.offset
522             # Request the contents again, since changing the size requires that
523             # the data grows. This should not fail, but check it to be sure.
524             if not self.ObtainContents():
525                 self.Raise('Cannot obtain contents when expanding entry')