binman: Obtain the list of device trees from the config
[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         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
165     def GetDefaultFilename(self):
166         return None
167
168     def GetFdtSet(self):
169         """Get the set of device trees used by this entry
170
171         Returns:
172             Set containing the filename from this entry, if it is a .dtb, else
173             an empty set
174         """
175         fname = self.GetDefaultFilename()
176         # It would be better to use isinstance(self, Entry_blob_dtb) here but
177         # we cannot access Entry_blob_dtb
178         if fname and fname.endswith('.dtb'):
179             return Set([fname])
180         return Set()
181
182     def AddMissingProperties(self):
183         """Add new properties to the device tree as needed for this entry"""
184         for prop in ['offset', 'size', 'image-pos']:
185             if not prop in self._node.props:
186                 state.AddZeroProp(self._node, prop)
187
188     def SetCalculatedProperties(self):
189         """Set the value of device-tree properties calculated by binman"""
190         state.SetInt(self._node, 'offset', self.offset)
191         state.SetInt(self._node, 'size', self.size)
192         state.SetInt(self._node, 'image-pos', self.image_pos)
193
194     def ProcessFdt(self, fdt):
195         return True
196
197     def SetPrefix(self, prefix):
198         """Set the name prefix for a node
199
200         Args:
201             prefix: Prefix to set, or '' to not use a prefix
202         """
203         if prefix:
204             self.name = prefix + self.name
205
206     def SetContents(self, data):
207         """Set the contents of an entry
208
209         This sets both the data and content_size properties
210
211         Args:
212             data: Data to set to the contents (string)
213         """
214         self.data = data
215         self.contents_size = len(self.data)
216
217     def ProcessContentsUpdate(self, data):
218         """Update the contens of an entry, after the size is fixed
219
220         This checks that the new data is the same size as the old.
221
222         Args:
223             data: Data to set to the contents (string)
224
225         Raises:
226             ValueError if the new data size is not the same as the old
227         """
228         if len(data) != self.contents_size:
229             self.Raise('Cannot update entry size from %d to %d' %
230                        (len(data), self.contents_size))
231         self.SetContents(data)
232
233     def ObtainContents(self):
234         """Figure out the contents of an entry.
235
236         Returns:
237             True if the contents were found, False if another call is needed
238             after the other entries are processed.
239         """
240         # No contents by default: subclasses can implement this
241         return True
242
243     def Pack(self, offset):
244         """Figure out how to pack the entry into the section
245
246         Most of the time the entries are not fully specified. There may be
247         an alignment but no size. In that case we take the size from the
248         contents of the entry.
249
250         If an entry has no hard-coded offset, it will be placed at @offset.
251
252         Once this function is complete, both the offset and size of the
253         entry will be know.
254
255         Args:
256             Current section offset pointer
257
258         Returns:
259             New section offset pointer (after this entry)
260         """
261         if self.offset is None:
262             if self.offset_unset:
263                 self.Raise('No offset set with offset-unset: should another '
264                            'entry provide this correct offset?')
265             self.offset = tools.Align(offset, self.align)
266         needed = self.pad_before + self.contents_size + self.pad_after
267         needed = tools.Align(needed, self.align_size)
268         size = self.size
269         if not size:
270             size = needed
271         new_offset = self.offset + size
272         aligned_offset = tools.Align(new_offset, self.align_end)
273         if aligned_offset != new_offset:
274             size = aligned_offset - self.offset
275             new_offset = aligned_offset
276
277         if not self.size:
278             self.size = size
279
280         if self.size < needed:
281             self.Raise("Entry contents size is %#x (%d) but entry size is "
282                        "%#x (%d)" % (needed, needed, self.size, self.size))
283         # Check that the alignment is correct. It could be wrong if the
284         # and offset or size values were provided (i.e. not calculated), but
285         # conflict with the provided alignment values
286         if self.size != tools.Align(self.size, self.align_size):
287             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
288                   (self.size, self.size, self.align_size, self.align_size))
289         if self.offset != tools.Align(self.offset, self.align):
290             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
291                   (self.offset, self.offset, self.align, self.align))
292
293         return new_offset
294
295     def Raise(self, msg):
296         """Convenience function to raise an error referencing a node"""
297         raise ValueError("Node '%s': %s" % (self._node.path, msg))
298
299     def GetEntryArgsOrProps(self, props, required=False):
300         """Return the values of a set of properties
301
302         Args:
303             props: List of EntryArg objects
304
305         Raises:
306             ValueError if a property is not found
307         """
308         values = []
309         missing = []
310         for prop in props:
311             python_prop = prop.name.replace('-', '_')
312             if hasattr(self, python_prop):
313                 value = getattr(self, python_prop)
314             else:
315                 value = None
316             if value is None:
317                 value = self.GetArg(prop.name, prop.datatype)
318             if value is None and required:
319                 missing.append(prop.name)
320             values.append(value)
321         if missing:
322             self.Raise('Missing required properties/entry args: %s' %
323                        (', '.join(missing)))
324         return values
325
326     def GetPath(self):
327         """Get the path of a node
328
329         Returns:
330             Full path of the node for this entry
331         """
332         return self._node.path
333
334     def GetData(self):
335         return self.data
336
337     def GetOffsets(self):
338         return {}
339
340     def SetOffsetSize(self, pos, size):
341         self.offset = pos
342         self.size = size
343
344     def SetImagePos(self, image_pos):
345         """Set the position in the image
346
347         Args:
348             image_pos: Position of this entry in the image
349         """
350         self.image_pos = image_pos + self.offset
351
352     def ProcessContents(self):
353         pass
354
355     def WriteSymbols(self, section):
356         """Write symbol values into binary files for access at run time
357
358         Args:
359           section: Section containing the entry
360         """
361         pass
362
363     def CheckOffset(self):
364         """Check that the entry offsets are correct
365
366         This is used for entries which have extra offset requirements (other
367         than having to be fully inside their section). Sub-classes can implement
368         this function and raise if there is a problem.
369         """
370         pass
371
372     @staticmethod
373     def WriteMapLine(fd, indent, name, offset, size, image_pos):
374         print('%08x  %s%08x  %08x  %s' % (image_pos, ' ' * indent, offset,
375                                           size, name), file=fd)
376
377     def WriteMap(self, fd, indent):
378         """Write a map of the entry to a .map file
379
380         Args:
381             fd: File to write the map to
382             indent: Curent indent level of map (0=none, 1=one level, etc.)
383         """
384         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
385                           self.image_pos)
386
387     def GetEntries(self):
388         """Return a list of entries contained by this entry
389
390         Returns:
391             List of entries, or None if none. A normal entry has no entries
392                 within it so will return None
393         """
394         return None
395
396     def GetArg(self, name, datatype=str):
397         """Get the value of an entry argument or device-tree-node property
398
399         Some node properties can be provided as arguments to binman. First check
400         the entry arguments, and fall back to the device tree if not found
401
402         Args:
403             name: Argument name
404             datatype: Data type (str or int)
405
406         Returns:
407             Value of argument as a string or int, or None if no value
408
409         Raises:
410             ValueError if the argument cannot be converted to in
411         """
412         value = state.GetEntryArg(name)
413         if value is not None:
414             if datatype == int:
415                 try:
416                     value = int(value)
417                 except ValueError:
418                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
419                                (name, value))
420             elif datatype == str:
421                 pass
422             else:
423                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
424                                  datatype)
425         else:
426             value = fdt_util.GetDatatype(self._node, name, datatype)
427         return value
428
429     @staticmethod
430     def WriteDocs(modules, test_missing=None):
431         """Write out documentation about the various entry types to stdout
432
433         Args:
434             modules: List of modules to include
435             test_missing: Used for testing. This is a module to report
436                 as missing
437         """
438         print('''Binman Entry Documentation
439 ===========================
440
441 This file describes the entry types supported by binman. These entry types can
442 be placed in an image one by one to build up a final firmware image. It is
443 fairly easy to create new entry types. Just add a new file to the 'etype'
444 directory. You can use the existing entries as examples.
445
446 Note that some entries are subclasses of others, using and extending their
447 features to produce new behaviours.
448
449
450 ''')
451         modules = sorted(modules)
452
453         # Don't show the test entry
454         if '_testing' in modules:
455             modules.remove('_testing')
456         missing = []
457         for name in modules:
458             module = Entry.Lookup(name, name, name)
459             docs = getattr(module, '__doc__')
460             if test_missing == name:
461                 docs = None
462             if docs:
463                 lines = docs.splitlines()
464                 first_line = lines[0]
465                 rest = [line[4:] for line in lines[1:]]
466                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
467                 print(hdr)
468                 print('-' * len(hdr))
469                 print('\n'.join(rest))
470                 print()
471                 print()
472             else:
473                 missing.append(name)
474
475         if missing:
476             raise ValueError('Documentation is missing for modules: %s' %
477                              ', '.join(missing))
478
479     def GetUniqueName(self):
480         """Get a unique name for a node
481
482         Returns:
483             String containing a unique name for a node, consisting of the name
484             of all ancestors (starting from within the 'binman' node) separated
485             by a dot ('.'). This can be useful for generating unique filesnames
486             in the output directory.
487         """
488         name = self.name
489         node = self._node
490         while node.parent:
491             node = node.parent
492             if node.name == 'binman':
493                 break
494             name = '%s.%s' % (node.name, name)
495         return name