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