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