Merge branch 'master' of git://git.denx.de/u-boot-samsung
[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 = 0
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         if self._end_4gb and not self._size:
83             self._Raise("Section size must be provided when using end-at-4gb")
84         if self._end_4gb:
85             self._skip_at_start = 0x100000000 - self._size
86         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
87
88     def _ReadEntries(self):
89         for node in self._node.subnodes:
90             entry = Entry.Create(self, node)
91             entry.SetPrefix(self._name_prefix)
92             self._entries[node.name] = entry
93
94     def SetOffset(self, offset):
95         self._offset = offset
96
97     def AddMissingProperties(self):
98         """Add new properties to the device tree as needed for this entry"""
99         for prop in ['offset', 'size', 'image-pos']:
100             if not prop in self._node.props:
101                 self._node.AddZeroProp(prop)
102         for entry in self._entries.values():
103             entry.AddMissingProperties()
104
105     def SetCalculatedProperties(self):
106         self._node.SetInt('offset', self._offset)
107         self._node.SetInt('size', self._size)
108         self._node.SetInt('image-pos', self._image_pos)
109         for entry in self._entries.values():
110             entry.SetCalculatedProperties()
111
112     def ProcessFdt(self, fdt):
113         todo = self._entries.values()
114         for passnum in range(3):
115             next_todo = []
116             for entry in todo:
117                 if not entry.ProcessFdt(fdt):
118                     next_todo.append(entry)
119             todo = next_todo
120             if not todo:
121                 break
122         if todo:
123             self._Raise('Internal error: Could not complete processing of Fdt: '
124                         'remaining %s' % todo)
125         return True
126
127     def CheckSize(self):
128         """Check that the section contents does not exceed its size, etc."""
129         contents_size = 0
130         for entry in self._entries.values():
131             contents_size = max(contents_size, entry.offset + entry.size)
132
133         contents_size -= self._skip_at_start
134
135         size = self._size
136         if not size:
137             size = self._pad_before + contents_size + self._pad_after
138             size = tools.Align(size, self._align_size)
139
140         if self._size and contents_size > self._size:
141             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
142                        (contents_size, contents_size, self._size, self._size))
143         if not self._size:
144             self._size = size
145         if self._size != tools.Align(self._size, self._align_size):
146             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
147                   (self._size, self._size, self._align_size, self._align_size))
148         return size
149
150     def _Raise(self, msg):
151         """Raises an error for this section
152
153         Args:
154             msg: Error message to use in the raise string
155         Raises:
156             ValueError()
157         """
158         raise ValueError("Section '%s': %s" % (self._node.path, msg))
159
160     def GetPath(self):
161         """Get the path of an image (in the FDT)
162
163         Returns:
164             Full path of the node for this image
165         """
166         return self._node.path
167
168     def FindEntryType(self, etype):
169         """Find an entry type in the section
170
171         Args:
172             etype: Entry type to find
173         Returns:
174             entry matching that type, or None if not found
175         """
176         for entry in self._entries.values():
177             if entry.etype == etype:
178                 return entry
179         return None
180
181     def GetEntryContents(self):
182         """Call ObtainContents() for each entry
183
184         This calls each entry's ObtainContents() a few times until they all
185         return True. We stop calling an entry's function once it returns
186         True. This allows the contents of one entry to depend on another.
187
188         After 3 rounds we give up since it's likely an error.
189         """
190         todo = self._entries.values()
191         for passnum in range(3):
192             next_todo = []
193             for entry in todo:
194                 if not entry.ObtainContents():
195                     next_todo.append(entry)
196             todo = next_todo
197             if not todo:
198                 break
199         if todo:
200             self._Raise('Internal error: Could not complete processing of '
201                         'contents: remaining %s' % todo)
202         return True
203
204     def _SetEntryOffsetSize(self, name, offset, size):
205         """Set the offset and size of an entry
206
207         Args:
208             name: Entry name to update
209             offset: New offset
210             size: New size
211         """
212         entry = self._entries.get(name)
213         if not entry:
214             self._Raise("Unable to set offset/size for unknown entry '%s'" %
215                         name)
216         entry.SetOffsetSize(self._skip_at_start + offset, size)
217
218     def GetEntryOffsets(self):
219         """Handle entries that want to set the offset/size of other entries
220
221         This calls each entry's GetOffsets() method. If it returns a list
222         of entries to update, it updates them.
223         """
224         for entry in self._entries.values():
225             offset_dict = entry.GetOffsets()
226             for name, info in offset_dict.iteritems():
227                 self._SetEntryOffsetSize(name, *info)
228
229     def PackEntries(self):
230         """Pack all entries into the section"""
231         offset = self._skip_at_start
232         for entry in self._entries.values():
233             offset = entry.Pack(offset)
234         self._size = self.CheckSize()
235
236     def _SortEntries(self):
237         """Sort entries by offset"""
238         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
239         self._entries.clear()
240         for entry in entries:
241             self._entries[entry._node.name] = entry
242
243     def CheckEntries(self):
244         """Check that entries do not overlap or extend outside the section"""
245         if self._sort:
246             self._SortEntries()
247         offset = 0
248         prev_name = 'None'
249         for entry in self._entries.values():
250             entry.CheckOffset()
251             if (entry.offset < self._skip_at_start or
252                 entry.offset >= self._skip_at_start + self._size):
253                 entry.Raise("Offset %#x (%d) is outside the section starting "
254                             "at %#x (%d)" %
255                             (entry.offset, entry.offset, self._skip_at_start,
256                              self._skip_at_start))
257             if entry.offset < offset:
258                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
259                             "ending at %#x (%d)" %
260                             (entry.offset, entry.offset, prev_name, offset, offset))
261             offset = entry.offset + entry.size
262             prev_name = entry.GetPath()
263
264     def SetImagePos(self, image_pos):
265         self._image_pos = image_pos
266         for entry in self._entries.values():
267             entry.SetImagePos(image_pos)
268
269     def ProcessEntryContents(self):
270         """Call the ProcessContents() method for each entry
271
272         This is intended to adjust the contents as needed by the entry type.
273         """
274         for entry in self._entries.values():
275             entry.ProcessContents()
276
277     def WriteSymbols(self):
278         """Write symbol values into binary files for access at run time"""
279         for entry in self._entries.values():
280             entry.WriteSymbols(self)
281
282     def BuildSection(self, fd, base_offset):
283         """Write the section to a file"""
284         fd.seek(base_offset)
285         fd.write(self.GetData())
286
287     def GetData(self):
288         """Get the contents of the section"""
289         section_data = chr(self._pad_byte) * self._size
290
291         for entry in self._entries.values():
292             data = entry.GetData()
293             base = self._pad_before + entry.offset - self._skip_at_start
294             section_data = (section_data[:base] + data +
295                             section_data[base + len(data):])
296         return section_data
297
298     def LookupSymbol(self, sym_name, optional, msg):
299         """Look up a symbol in an ELF file
300
301         Looks up a symbol in an ELF file. Only entry types which come from an
302         ELF image can be used by this function.
303
304         At present the only entry property supported is offset.
305
306         Args:
307             sym_name: Symbol name in the ELF file to look up in the format
308                 _binman_<entry>_prop_<property> where <entry> is the name of
309                 the entry and <property> is the property to find (e.g.
310                 _binman_u_boot_prop_offset). As a special case, you can append
311                 _any to <entry> to have it search for any matching entry. E.g.
312                 _binman_u_boot_any_prop_offset will match entries called u-boot,
313                 u-boot-img and u-boot-nodtb)
314             optional: True if the symbol is optional. If False this function
315                 will raise if the symbol is not found
316             msg: Message to display if an error occurs
317
318         Returns:
319             Value that should be assigned to that symbol, or None if it was
320                 optional and not found
321
322         Raises:
323             ValueError if the symbol is invalid or not found, or references a
324                 property which is not supported
325         """
326         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
327         if not m:
328             raise ValueError("%s: Symbol '%s' has invalid format" %
329                              (msg, sym_name))
330         entry_name, prop_name = m.groups()
331         entry_name = entry_name.replace('_', '-')
332         entry = self._entries.get(entry_name)
333         if not entry:
334             if entry_name.endswith('-any'):
335                 root = entry_name[:-4]
336                 for name in self._entries:
337                     if name.startswith(root):
338                         rest = name[len(root):]
339                         if rest in ['', '-img', '-nodtb']:
340                             entry = self._entries[name]
341         if not entry:
342             err = ("%s: Entry '%s' not found in list (%s)" %
343                    (msg, entry_name, ','.join(self._entries.keys())))
344             if optional:
345                 print('Warning: %s' % err, file=sys.stderr)
346                 return None
347             raise ValueError(err)
348         if prop_name == 'offset':
349             return entry.offset
350         elif prop_name == 'image_pos':
351             return entry.image_pos
352         else:
353             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
354
355     def GetEntries(self):
356         """Get the number of entries in a section
357
358         Returns:
359             Number of entries in a section
360         """
361         return self._entries
362
363     def GetSize(self):
364         """Get the size of a section in bytes
365
366         This is only meaningful if the section has a pre-defined size, or the
367         entries within it have been packed, so that the size has been
368         calculated.
369
370         Returns:
371             Entry size in bytes
372         """
373         return self._size
374
375     def WriteMap(self, fd, indent):
376         """Write a map of the section to a .map file
377
378         Args:
379             fd: File to write the map to
380         """
381         Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
382                            self._image_pos)
383         for entry in self._entries.values():
384             entry.WriteMap(fd, indent + 1)
385
386     def GetContentsByPhandle(self, phandle, source_entry):
387         """Get the data contents of an entry specified by a phandle
388
389         This uses a phandle to look up a node and and find the entry
390         associated with it. Then it returnst he contents of that entry.
391
392         Args:
393             phandle: Phandle to look up (integer)
394             source_entry: Entry containing that phandle (used for error
395                 reporting)
396
397         Returns:
398             data from associated entry (as a string), or None if not found
399         """
400         node = self._node.GetFdt().LookupPhandle(phandle)
401         if not node:
402             source_entry.Raise("Cannot find node for phandle %d" % phandle)
403         for entry in self._entries.values():
404             if entry._node == node:
405                 if entry.data is None:
406                     return None
407                 return entry.data
408         source_entry.Raise("Cannot find entry for node '%s'" % node.name)