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