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