binman: Add an entry for a Chromium vblock
[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):
353         print('%s%08x  %08x  %s' % (' ' * indent, offset, size, name), file=fd)
354
355     def WriteMap(self, fd, indent):
356         """Write a map of the entry to a .map file
357
358         Args:
359             fd: File to write the map to
360             indent: Curent indent level of map (0=none, 1=one level, etc.)
361         """
362         self.WriteMapLine(fd, indent, self.name, self.offset, self.size)
363
364     def GetEntries(self):
365         """Return a list of entries contained by this entry
366
367         Returns:
368             List of entries, or None if none. A normal entry has no entries
369                 within it so will return None
370         """
371         return None
372
373     def GetArg(self, name, datatype=str):
374         """Get the value of an entry argument or device-tree-node property
375
376         Some node properties can be provided as arguments to binman. First check
377         the entry arguments, and fall back to the device tree if not found
378
379         Args:
380             name: Argument name
381             datatype: Data type (str or int)
382
383         Returns:
384             Value of argument as a string or int, or None if no value
385
386         Raises:
387             ValueError if the argument cannot be converted to in
388         """
389         value = control.GetEntryArg(name)
390         if value is not None:
391             if datatype == int:
392                 try:
393                     value = int(value)
394                 except ValueError:
395                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
396                                (name, value))
397             elif datatype == str:
398                 pass
399             else:
400                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
401                                  datatype)
402         else:
403             value = fdt_util.GetDatatype(self._node, name, datatype)
404         return value
405
406     @staticmethod
407     def WriteDocs(modules, test_missing=None):
408         """Write out documentation about the various entry types to stdout
409
410         Args:
411             modules: List of modules to include
412             test_missing: Used for testing. This is a module to report
413                 as missing
414         """
415         print('''Binman Entry Documentation
416 ===========================
417
418 This file describes the entry types supported by binman. These entry types can
419 be placed in an image one by one to build up a final firmware image. It is
420 fairly easy to create new entry types. Just add a new file to the 'etype'
421 directory. You can use the existing entries as examples.
422
423 Note that some entries are subclasses of others, using and extending their
424 features to produce new behaviours.
425
426
427 ''')
428         modules = sorted(modules)
429
430         # Don't show the test entry
431         if '_testing' in modules:
432             modules.remove('_testing')
433         missing = []
434         for name in modules:
435             module = Entry.Lookup(name, name, name)
436             docs = getattr(module, '__doc__')
437             if test_missing == name:
438                 docs = None
439             if docs:
440                 lines = docs.splitlines()
441                 first_line = lines[0]
442                 rest = [line[4:] for line in lines[1:]]
443                 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
444                 print(hdr)
445                 print('-' * len(hdr))
446                 print('\n'.join(rest))
447                 print()
448                 print()
449             else:
450                 missing.append(name)
451
452         if missing:
453             raise ValueError('Documentation is missing for modules: %s' %
454                              ', '.join(missing))