binman: Add a new 'image-pos' property
[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 # importlib was introduced in Python 2.7 but there was a report of it not
10 # working in 2.7.12, so we work around this:
11 # http://lists.denx.de/pipermail/u-boot/2016-October/269729.html
12 try:
13     import importlib
14     have_importlib = True
15 except:
16     have_importlib = False
17
18 import fdt_util
19 import os
20 import sys
21 import tools
22
23 modules = {}
24
25 our_path = os.path.dirname(os.path.realpath(__file__))
26
27 class Entry(object):
28     """An Entry in the section
29
30     An entry corresponds to a single node in the device-tree description
31     of the section. Each entry ends up being a part of the final section.
32     Entries can be placed either right next to each other, or with padding
33     between them. The type of the entry determines the data that is in it.
34
35     This class is not used by itself. All entry objects are subclasses of
36     Entry.
37
38     Attributes:
39         section: Section object containing this entry
40         node: The node that created this entry
41         offset: Offset of entry within the section, None if not known yet (in
42             which case it will be calculated by Pack())
43         size: Entry size in bytes, None if not known
44         contents_size: Size of contents in bytes, 0 by default
45         align: Entry start offset alignment, or None
46         align_size: Entry size alignment, or None
47         align_end: Entry end offset alignment, or None
48         pad_before: Number of pad bytes before the contents, 0 if none
49         pad_after: Number of pad bytes after the contents, 0 if none
50         data: Contents of entry (string of bytes)
51     """
52     def __init__(self, section, etype, node, read_node=True, name_prefix=''):
53         self.section = section
54         self.etype = etype
55         self._node = node
56         self.name = node and (name_prefix + node.name) or 'none'
57         self.offset = None
58         self.size = None
59         self.data = ''
60         self.contents_size = 0
61         self.align = None
62         self.align_size = None
63         self.align_end = None
64         self.pad_before = 0
65         self.pad_after = 0
66         self.offset_unset = False
67         self.image_pos = None
68         if read_node:
69             self.ReadNode()
70
71     @staticmethod
72     def Create(section, node, etype=None):
73         """Create a new entry for a node.
74
75         Args:
76             section:  Section object containing this node
77             node:   Node object containing information about the entry to create
78             etype:  Entry type to use, or None to work it out (used for tests)
79
80         Returns:
81             A new Entry object of the correct type (a subclass of Entry)
82         """
83         if not etype:
84             etype = fdt_util.GetString(node, 'type', node.name)
85
86         # Convert something like 'u-boot@0' to 'u_boot' since we are only
87         # interested in the type.
88         module_name = etype.replace('-', '_')
89         if '@' in module_name:
90             module_name = module_name.split('@')[0]
91         module = modules.get(module_name)
92
93         # Also allow entry-type modules to be brought in from the etype directory.
94
95         # Import the module if we have not already done so.
96         if not module:
97             old_path = sys.path
98             sys.path.insert(0, os.path.join(our_path, 'etype'))
99             try:
100                 if have_importlib:
101                     module = importlib.import_module(module_name)
102                 else:
103                     module = __import__(module_name)
104             except ImportError:
105                 raise ValueError("Unknown entry type '%s' in node '%s'" %
106                         (etype, node.path))
107             finally:
108                 sys.path = old_path
109             modules[module_name] = module
110
111         # Call its constructor to get the object we want.
112         obj = getattr(module, 'Entry_%s' % module_name)
113         return obj(section, etype, node)
114
115     def ReadNode(self):
116         """Read entry information from the node
117
118         This reads all the fields we recognise from the node, ready for use.
119         """
120         self.offset = fdt_util.GetInt(self._node, 'offset')
121         self.size = fdt_util.GetInt(self._node, 'size')
122         self.align = fdt_util.GetInt(self._node, 'align')
123         if tools.NotPowerOfTwo(self.align):
124             raise ValueError("Node '%s': Alignment %s must be a power of two" %
125                              (self._node.path, self.align))
126         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
127         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
128         self.align_size = fdt_util.GetInt(self._node, 'align-size')
129         if tools.NotPowerOfTwo(self.align_size):
130             raise ValueError("Node '%s': Alignment size %s must be a power "
131                              "of two" % (self._node.path, self.align_size))
132         self.align_end = fdt_util.GetInt(self._node, 'align-end')
133         self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
134
135     def AddMissingProperties(self):
136         """Add new properties to the device tree as needed for this entry"""
137         for prop in ['offset', 'size', 'image-pos']:
138             if not prop in self._node.props:
139                 self._node.AddZeroProp(prop)
140
141     def SetCalculatedProperties(self):
142         """Set the value of device-tree properties calculated by binman"""
143         self._node.SetInt('offset', self.offset)
144         self._node.SetInt('size', self.size)
145         self._node.SetInt('image-pos', self.image_pos)
146
147     def ProcessFdt(self, fdt):
148         return True
149
150     def SetPrefix(self, prefix):
151         """Set the name prefix for a node
152
153         Args:
154             prefix: Prefix to set, or '' to not use a prefix
155         """
156         if prefix:
157             self.name = prefix + self.name
158
159     def SetContents(self, data):
160         """Set the contents of an entry
161
162         This sets both the data and content_size properties
163
164         Args:
165             data: Data to set to the contents (string)
166         """
167         self.data = data
168         self.contents_size = len(self.data)
169
170     def ProcessContentsUpdate(self, data):
171         """Update the contens of an entry, after the size is fixed
172
173         This checks that the new data is the same size as the old.
174
175         Args:
176             data: Data to set to the contents (string)
177
178         Raises:
179             ValueError if the new data size is not the same as the old
180         """
181         if len(data) != self.contents_size:
182             self.Raise('Cannot update entry size from %d to %d' %
183                        (len(data), self.contents_size))
184         self.SetContents(data)
185
186     def ObtainContents(self):
187         """Figure out the contents of an entry.
188
189         Returns:
190             True if the contents were found, False if another call is needed
191             after the other entries are processed.
192         """
193         # No contents by default: subclasses can implement this
194         return True
195
196     def Pack(self, offset):
197         """Figure out how to pack the entry into the section
198
199         Most of the time the entries are not fully specified. There may be
200         an alignment but no size. In that case we take the size from the
201         contents of the entry.
202
203         If an entry has no hard-coded offset, it will be placed at @offset.
204
205         Once this function is complete, both the offset and size of the
206         entry will be know.
207
208         Args:
209             Current section offset pointer
210
211         Returns:
212             New section offset pointer (after this entry)
213         """
214         if self.offset is None:
215             if self.offset_unset:
216                 self.Raise('No offset set with offset-unset: should another '
217                            'entry provide this correct offset?')
218             self.offset = tools.Align(offset, self.align)
219         needed = self.pad_before + self.contents_size + self.pad_after
220         needed = tools.Align(needed, self.align_size)
221         size = self.size
222         if not size:
223             size = needed
224         new_offset = self.offset + size
225         aligned_offset = tools.Align(new_offset, self.align_end)
226         if aligned_offset != new_offset:
227             size = aligned_offset - self.offset
228             new_offset = aligned_offset
229
230         if not self.size:
231             self.size = size
232
233         if self.size < needed:
234             self.Raise("Entry contents size is %#x (%d) but entry size is "
235                        "%#x (%d)" % (needed, needed, self.size, self.size))
236         # Check that the alignment is correct. It could be wrong if the
237         # and offset or size values were provided (i.e. not calculated), but
238         # conflict with the provided alignment values
239         if self.size != tools.Align(self.size, self.align_size):
240             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
241                   (self.size, self.size, self.align_size, self.align_size))
242         if self.offset != tools.Align(self.offset, self.align):
243             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
244                   (self.offset, self.offset, self.align, self.align))
245
246         return new_offset
247
248     def Raise(self, msg):
249         """Convenience function to raise an error referencing a node"""
250         raise ValueError("Node '%s': %s" % (self._node.path, msg))
251
252     def GetPath(self):
253         """Get the path of a node
254
255         Returns:
256             Full path of the node for this entry
257         """
258         return self._node.path
259
260     def GetData(self):
261         return self.data
262
263     def GetOffsets(self):
264         return {}
265
266     def SetOffsetSize(self, pos, size):
267         self.offset = pos
268         self.size = size
269
270     def SetImagePos(self, image_pos):
271         """Set the position in the image
272
273         Args:
274             image_pos: Position of this entry in the image
275         """
276         self.image_pos = image_pos + self.offset
277
278     def ProcessContents(self):
279         pass
280
281     def WriteSymbols(self, section):
282         """Write symbol values into binary files for access at run time
283
284         Args:
285           section: Section containing the entry
286         """
287         pass
288
289     def CheckOffset(self):
290         """Check that the entry offsets are correct
291
292         This is used for entries which have extra offset requirements (other
293         than having to be fully inside their section). Sub-classes can implement
294         this function and raise if there is a problem.
295         """
296         pass
297
298     @staticmethod
299     def WriteMapLine(fd, indent, name, offset, size):
300         print('%s%08x  %08x  %s' % (' ' * indent, offset, size, name), file=fd)
301
302     def WriteMap(self, fd, indent):
303         """Write a map of the entry to a .map file
304
305         Args:
306             fd: File to write the map to
307             indent: Curent indent level of map (0=none, 1=one level, etc.)
308         """
309         self.WriteMapLine(fd, indent, self.name, self.offset, self.size)