Merge branch 'master' of git://git.denx.de/u-boot-sunxi
[oweals/u-boot.git] / tools / binman / bsection.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Base class for sections (collections of entries)
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import sys
12
13 import fdt_util
14 import re
15 import tools
16
17 class Section(object):
18     """A section which contains multiple entries
19
20     A section represents a collection of entries. There must be one or more
21     sections in an image. Sections are used to group entries together.
22
23     Attributes:
24         _node: Node object that contains the section definition in device tree
25         _size: Section size in bytes, or None if not known yet
26         _align_size: Section size alignment, or None
27         _pad_before: Number of bytes before the first entry starts. This
28             effectively changes the place where entry offset 0 starts
29         _pad_after: Number of bytes after the last entry ends. The last
30             entry will finish on or before this boundary
31         _pad_byte: Byte to use to pad the section where there is no entry
32         _sort: True if entries should be sorted by offset, False if they
33             must be in-order in the device tree description
34         _skip_at_start: Number of bytes before the first entry starts. These
35             effectively adjust the starting offset of entries. For example,
36             if _pad_before is 16, then the first entry would start at 16.
37             An entry with offset = 20 would in fact be written at offset 4
38             in the image file.
39         _end_4gb: Indicates that the section ends at the 4GB boundary. This is
40             used for x86 images, which want to use offsets such that a memory
41             address (like 0xff800000) is the first entry offset. This causes
42             _skip_at_start to be set to the starting memory address.
43         _name_prefix: Prefix to add to the name of all entries within this
44             section
45         _entries: OrderedDict() of entries
46     """
47     def __init__(self, name, node, test=False):
48         global entry
49         global Entry
50         import entry
51         from entry import Entry
52
53         self._name = name
54         self._node = node
55         self._offset = 0
56         self._size = None
57         self._align_size = None
58         self._pad_before = 0
59         self._pad_after = 0
60         self._pad_byte = 0
61         self._sort = False
62         self._skip_at_start = None
63         self._end_4gb = False
64         self._name_prefix = ''
65         self._entries = OrderedDict()
66         if not test:
67             self._ReadNode()
68             self._ReadEntries()
69
70     def _ReadNode(self):
71         """Read properties from the section node"""
72         self._size = fdt_util.GetInt(self._node, 'size')
73         self._align_size = fdt_util.GetInt(self._node, 'align-size')
74         if tools.NotPowerOfTwo(self._align_size):
75             self._Raise("Alignment size %s must be a power of two" %
76                         self._align_size)
77         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
78         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
79         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
80         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
81         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
82         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
83         if self._end_4gb:
84             if not self._size:
85                 self._Raise("Section size must be provided when using end-at-4gb")
86             if self._skip_at_start is not None:
87                 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
88             else:
89                 self._skip_at_start = 0x100000000 - self._size
90         else:
91             if self._skip_at_start is None:
92                 self._skip_at_start = 0
93         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
94
95     def _ReadEntries(self):
96         for node in self._node.subnodes:
97             entry = Entry.Create(self, node)
98             entry.SetPrefix(self._name_prefix)
99             self._entries[node.name] = entry
100
101     def SetOffset(self, offset):
102         self._offset = offset
103
104     def AddMissingProperties(self):
105         """Add new properties to the device tree as needed for this entry"""
106         for prop in ['offset', 'size', 'image-pos']:
107             if not prop in self._node.props:
108                 self._node.AddZeroProp(prop)
109         for entry in self._entries.values():
110             entry.AddMissingProperties()
111
112     def SetCalculatedProperties(self):
113         self._node.SetInt('offset', self._offset)
114         self._node.SetInt('size', self._size)
115         self._node.SetInt('image-pos', self._image_pos)
116         for entry in self._entries.values():
117             entry.SetCalculatedProperties()
118
119     def ProcessFdt(self, fdt):
120         todo = self._entries.values()
121         for passnum in range(3):
122             next_todo = []
123             for entry in todo:
124                 if not entry.ProcessFdt(fdt):
125                     next_todo.append(entry)
126             todo = next_todo
127             if not todo:
128                 break
129         if todo:
130             self._Raise('Internal error: Could not complete processing of Fdt: '
131                         'remaining %s' % todo)
132         return True
133
134     def CheckSize(self):
135         """Check that the section contents does not exceed its size, etc."""
136         contents_size = 0
137         for entry in self._entries.values():
138             contents_size = max(contents_size, entry.offset + entry.size)
139
140         contents_size -= self._skip_at_start
141
142         size = self._size
143         if not size:
144             size = self._pad_before + contents_size + self._pad_after
145             size = tools.Align(size, self._align_size)
146
147         if self._size and contents_size > self._size:
148             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
149                        (contents_size, contents_size, self._size, self._size))
150         if not self._size:
151             self._size = size
152         if self._size != tools.Align(self._size, self._align_size):
153             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
154                   (self._size, self._size, self._align_size, self._align_size))
155         return size
156
157     def _Raise(self, msg):
158         """Raises an error for this section
159
160         Args:
161             msg: Error message to use in the raise string
162         Raises:
163             ValueError()
164         """
165         raise ValueError("Section '%s': %s" % (self._node.path, msg))
166
167     def GetPath(self):
168         """Get the path of an image (in the FDT)
169
170         Returns:
171             Full path of the node for this image
172         """
173         return self._node.path
174
175     def FindEntryType(self, etype):
176         """Find an entry type in the section
177
178         Args:
179             etype: Entry type to find
180         Returns:
181             entry matching that type, or None if not found
182         """
183         for entry in self._entries.values():
184             if entry.etype == etype:
185                 return entry
186         return None
187
188     def GetEntryContents(self):
189         """Call ObtainContents() for each entry
190
191         This calls each entry's ObtainContents() a few times until they all
192         return True. We stop calling an entry's function once it returns
193         True. This allows the contents of one entry to depend on another.
194
195         After 3 rounds we give up since it's likely an error.
196         """
197         todo = self._entries.values()
198         for passnum in range(3):
199             next_todo = []
200             for entry in todo:
201                 if not entry.ObtainContents():
202                     next_todo.append(entry)
203             todo = next_todo
204             if not todo:
205                 break
206         if todo:
207             self._Raise('Internal error: Could not complete processing of '
208                         'contents: remaining %s' % todo)
209         return True
210
211     def _SetEntryOffsetSize(self, name, offset, size):
212         """Set the offset and size of an entry
213
214         Args:
215             name: Entry name to update
216             offset: New offset
217             size: New size
218         """
219         entry = self._entries.get(name)
220         if not entry:
221             self._Raise("Unable to set offset/size for unknown entry '%s'" %
222                         name)
223         entry.SetOffsetSize(self._skip_at_start + offset, size)
224
225     def GetEntryOffsets(self):
226         """Handle entries that want to set the offset/size of other entries
227
228         This calls each entry's GetOffsets() method. If it returns a list
229         of entries to update, it updates them.
230         """
231         for entry in self._entries.values():
232             offset_dict = entry.GetOffsets()
233             for name, info in offset_dict.iteritems():
234                 self._SetEntryOffsetSize(name, *info)
235
236     def PackEntries(self):
237         """Pack all entries into the section"""
238         offset = self._skip_at_start
239         for entry in self._entries.values():
240             offset = entry.Pack(offset)
241         self._size = self.CheckSize()
242
243     def _SortEntries(self):
244         """Sort entries by offset"""
245         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
246         self._entries.clear()
247         for entry in entries:
248             self._entries[entry._node.name] = entry
249
250     def CheckEntries(self):
251         """Check that entries do not overlap or extend outside the section"""
252         if self._sort:
253             self._SortEntries()
254         offset = 0
255         prev_name = 'None'
256         for entry in self._entries.values():
257             entry.CheckOffset()
258             if (entry.offset < self._skip_at_start or
259                 entry.offset >= self._skip_at_start + self._size):
260                 entry.Raise("Offset %#x (%d) is outside the section starting "
261                             "at %#x (%d)" %
262                             (entry.offset, entry.offset, self._skip_at_start,
263                              self._skip_at_start))
264             if entry.offset < offset:
265                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
266                             "ending at %#x (%d)" %
267                             (entry.offset, entry.offset, prev_name, offset, offset))
268             offset = entry.offset + entry.size
269             prev_name = entry.GetPath()
270
271     def SetImagePos(self, image_pos):
272         self._image_pos = image_pos
273         for entry in self._entries.values():
274             entry.SetImagePos(image_pos)
275
276     def ProcessEntryContents(self):
277         """Call the ProcessContents() method for each entry
278
279         This is intended to adjust the contents as needed by the entry type.
280         """
281         for entry in self._entries.values():
282             entry.ProcessContents()
283
284     def WriteSymbols(self):
285         """Write symbol values into binary files for access at run time"""
286         for entry in self._entries.values():
287             entry.WriteSymbols(self)
288
289     def BuildSection(self, fd, base_offset):
290         """Write the section to a file"""
291         fd.seek(base_offset)
292         fd.write(self.GetData())
293
294     def GetData(self):
295         """Get the contents of the section"""
296         section_data = chr(self._pad_byte) * self._size
297
298         for entry in self._entries.values():
299             data = entry.GetData()
300             base = self._pad_before + entry.offset - self._skip_at_start
301             section_data = (section_data[:base] + data +
302                             section_data[base + len(data):])
303         return section_data
304
305     def LookupSymbol(self, sym_name, optional, msg):
306         """Look up a symbol in an ELF file
307
308         Looks up a symbol in an ELF file. Only entry types which come from an
309         ELF image can be used by this function.
310
311         At present the only entry property supported is offset.
312
313         Args:
314             sym_name: Symbol name in the ELF file to look up in the format
315                 _binman_<entry>_prop_<property> where <entry> is the name of
316                 the entry and <property> is the property to find (e.g.
317                 _binman_u_boot_prop_offset). As a special case, you can append
318                 _any to <entry> to have it search for any matching entry. E.g.
319                 _binman_u_boot_any_prop_offset will match entries called u-boot,
320                 u-boot-img and u-boot-nodtb)
321             optional: True if the symbol is optional. If False this function
322                 will raise if the symbol is not found
323             msg: Message to display if an error occurs
324
325         Returns:
326             Value that should be assigned to that symbol, or None if it was
327                 optional and not found
328
329         Raises:
330             ValueError if the symbol is invalid or not found, or references a
331                 property which is not supported
332         """
333         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
334         if not m:
335             raise ValueError("%s: Symbol '%s' has invalid format" %
336                              (msg, sym_name))
337         entry_name, prop_name = m.groups()
338         entry_name = entry_name.replace('_', '-')
339         entry = self._entries.get(entry_name)
340         if not entry:
341             if entry_name.endswith('-any'):
342                 root = entry_name[:-4]
343                 for name in self._entries:
344                     if name.startswith(root):
345                         rest = name[len(root):]
346                         if rest in ['', '-img', '-nodtb']:
347                             entry = self._entries[name]
348         if not entry:
349             err = ("%s: Entry '%s' not found in list (%s)" %
350                    (msg, entry_name, ','.join(self._entries.keys())))
351             if optional:
352                 print('Warning: %s' % err, file=sys.stderr)
353                 return None
354             raise ValueError(err)
355         if prop_name == 'offset':
356             return entry.offset
357         elif prop_name == 'image_pos':
358             return entry.image_pos
359         else:
360             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
361
362     def GetEntries(self):
363         """Get the number of entries in a section
364
365         Returns:
366             Number of entries in a section
367         """
368         return self._entries
369
370     def GetSize(self):
371         """Get the size of a section in bytes
372
373         This is only meaningful if the section has a pre-defined size, or the
374         entries within it have been packed, so that the size has been
375         calculated.
376
377         Returns:
378             Entry size in bytes
379         """
380         return self._size
381
382     def WriteMap(self, fd, indent):
383         """Write a map of the section to a .map file
384
385         Args:
386             fd: File to write the map to
387         """
388         Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
389                            self._image_pos)
390         for entry in self._entries.values():
391             entry.WriteMap(fd, indent + 1)
392
393     def GetContentsByPhandle(self, phandle, source_entry):
394         """Get the data contents of an entry specified by a phandle
395
396         This uses a phandle to look up a node and and find the entry
397         associated with it. Then it returnst he contents of that entry.
398
399         Args:
400             phandle: Phandle to look up (integer)
401             source_entry: Entry containing that phandle (used for error
402                 reporting)
403
404         Returns:
405             data from associated entry (as a string), or None if not found
406         """
407         node = self._node.GetFdt().LookupPhandle(phandle)
408         if not node:
409             source_entry.Raise("Cannot find node for phandle %d" % phandle)
410         for entry in self._entries.values():
411             if entry._node == node:
412                 if entry.data is None:
413                     return None
414                 return entry.data
415         source_entry.Raise("Cannot find entry for node '%s'" % node.name)