binman: Rename 'position' to 'offset'
[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._node = node
54         self._offset = 0
55         self._size = None
56         self._align_size = None
57         self._pad_before = 0
58         self._pad_after = 0
59         self._pad_byte = 0
60         self._sort = False
61         self._skip_at_start = 0
62         self._end_4gb = False
63         self._name_prefix = ''
64         self._entries = OrderedDict()
65         if not test:
66             self._ReadNode()
67             self._ReadEntries()
68
69     def _ReadNode(self):
70         """Read properties from the section node"""
71         self._size = fdt_util.GetInt(self._node, 'size')
72         self._align_size = fdt_util.GetInt(self._node, 'align-size')
73         if tools.NotPowerOfTwo(self._align_size):
74             self._Raise("Alignment size %s must be a power of two" %
75                         self._align_size)
76         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
77         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
78         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
79         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
80         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
81         if self._end_4gb and not self._size:
82             self._Raise("Section size must be provided when using end-at-4gb")
83         if self._end_4gb:
84             self._skip_at_start = 0x100000000 - self._size
85         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
86
87     def _ReadEntries(self):
88         for node in self._node.subnodes:
89             entry = Entry.Create(self, node)
90             entry.SetPrefix(self._name_prefix)
91             self._entries[node.name] = entry
92
93     def AddMissingProperties(self):
94         for entry in self._entries.values():
95             entry.AddMissingProperties()
96
97     def SetCalculatedProperties(self):
98         for entry in self._entries.values():
99             entry.SetCalculatedProperties()
100
101     def ProcessFdt(self, fdt):
102         todo = self._entries.values()
103         for passnum in range(3):
104             next_todo = []
105             for entry in todo:
106                 if not entry.ProcessFdt(fdt):
107                     next_todo.append(entry)
108             todo = next_todo
109             if not todo:
110                 break
111         if todo:
112             self._Raise('Internal error: Could not complete processing of Fdt: '
113                         'remaining %s' % todo)
114         return True
115
116     def CheckSize(self):
117         """Check that the section contents does not exceed its size, etc."""
118         contents_size = 0
119         for entry in self._entries.values():
120             contents_size = max(contents_size, entry.offset + entry.size)
121
122         contents_size -= self._skip_at_start
123
124         size = self._size
125         if not size:
126             size = self._pad_before + contents_size + self._pad_after
127             size = tools.Align(size, self._align_size)
128
129         if self._size and contents_size > self._size:
130             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
131                        (contents_size, contents_size, self._size, self._size))
132         if not self._size:
133             self._size = size
134         if self._size != tools.Align(self._size, self._align_size):
135             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
136                   (self._size, self._size, self._align_size, self._align_size))
137         return size
138
139     def _Raise(self, msg):
140         """Raises an error for this section
141
142         Args:
143             msg: Error message to use in the raise string
144         Raises:
145             ValueError()
146         """
147         raise ValueError("Section '%s': %s" % (self._node.path, msg))
148
149     def GetPath(self):
150         """Get the path of an image (in the FDT)
151
152         Returns:
153             Full path of the node for this image
154         """
155         return self._node.path
156
157     def FindEntryType(self, etype):
158         """Find an entry type in the section
159
160         Args:
161             etype: Entry type to find
162         Returns:
163             entry matching that type, or None if not found
164         """
165         for entry in self._entries.values():
166             if entry.etype == etype:
167                 return entry
168         return None
169
170     def GetEntryContents(self):
171         """Call ObtainContents() for each entry
172
173         This calls each entry's ObtainContents() a few times until they all
174         return True. We stop calling an entry's function once it returns
175         True. This allows the contents of one entry to depend on another.
176
177         After 3 rounds we give up since it's likely an error.
178         """
179         todo = self._entries.values()
180         for passnum in range(3):
181             next_todo = []
182             for entry in todo:
183                 if not entry.ObtainContents():
184                     next_todo.append(entry)
185             todo = next_todo
186             if not todo:
187                 break
188         if todo:
189             self._Raise('Internal error: Could not complete processing of '
190                         'contents: remaining %s' % todo)
191         return True
192
193     def _SetEntryOffsetSize(self, name, offset, size):
194         """Set the offset and size of an entry
195
196         Args:
197             name: Entry name to update
198             offset: New offset
199             size: New size
200         """
201         entry = self._entries.get(name)
202         if not entry:
203             self._Raise("Unable to set offset/size for unknown entry '%s'" %
204                         name)
205         entry.SetOffsetSize(self._skip_at_start + offset, size)
206
207     def GetEntryOffsets(self):
208         """Handle entries that want to set the offset/size of other entries
209
210         This calls each entry's GetOffsets() method. If it returns a list
211         of entries to update, it updates them.
212         """
213         for entry in self._entries.values():
214             offset_dict = entry.GetOffsets()
215             for name, info in offset_dict.iteritems():
216                 self._SetEntryOffsetSize(name, *info)
217
218     def PackEntries(self):
219         """Pack all entries into the section"""
220         offset = self._skip_at_start
221         for entry in self._entries.values():
222             offset = entry.Pack(offset)
223         self._size = self.CheckSize()
224
225     def _SortEntries(self):
226         """Sort entries by offset"""
227         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
228         self._entries.clear()
229         for entry in entries:
230             self._entries[entry._node.name] = entry
231
232     def CheckEntries(self):
233         """Check that entries do not overlap or extend outside the section"""
234         if self._sort:
235             self._SortEntries()
236         offset = 0
237         prev_name = 'None'
238         for entry in self._entries.values():
239             entry.CheckOffset()
240             if (entry.offset < self._skip_at_start or
241                 entry.offset >= self._skip_at_start + self._size):
242                 entry.Raise("Offset %#x (%d) is outside the section starting "
243                             "at %#x (%d)" %
244                             (entry.offset, entry.offset, self._skip_at_start,
245                              self._skip_at_start))
246             if entry.offset < offset:
247                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
248                             "ending at %#x (%d)" %
249                             (entry.offset, entry.offset, prev_name, offset, offset))
250             offset = entry.offset + entry.size
251             prev_name = entry.GetPath()
252
253     def ProcessEntryContents(self):
254         """Call the ProcessContents() method for each entry
255
256         This is intended to adjust the contents as needed by the entry type.
257         """
258         for entry in self._entries.values():
259             entry.ProcessContents()
260
261     def WriteSymbols(self):
262         """Write symbol values into binary files for access at run time"""
263         for entry in self._entries.values():
264             entry.WriteSymbols(self)
265
266     def BuildSection(self, fd, base_offset):
267         """Write the section to a file"""
268         fd.seek(base_offset)
269         fd.write(self.GetData())
270
271     def GetData(self):
272         """Write the section to a file"""
273         section_data = chr(self._pad_byte) * self._size
274
275         for entry in self._entries.values():
276             data = entry.GetData()
277             base = self._pad_before + entry.offset - self._skip_at_start
278             section_data = (section_data[:base] + data +
279                             section_data[base + len(data):])
280         return section_data
281
282     def LookupSymbol(self, sym_name, optional, msg):
283         """Look up a symbol in an ELF file
284
285         Looks up a symbol in an ELF file. Only entry types which come from an
286         ELF image can be used by this function.
287
288         At present the only entry property supported is offset.
289
290         Args:
291             sym_name: Symbol name in the ELF file to look up in the format
292                 _binman_<entry>_prop_<property> where <entry> is the name of
293                 the entry and <property> is the property to find (e.g.
294                 _binman_u_boot_prop_offset). As a special case, you can append
295                 _any to <entry> to have it search for any matching entry. E.g.
296                 _binman_u_boot_any_prop_offset will match entries called u-boot,
297                 u-boot-img and u-boot-nodtb)
298             optional: True if the symbol is optional. If False this function
299                 will raise if the symbol is not found
300             msg: Message to display if an error occurs
301
302         Returns:
303             Value that should be assigned to that symbol, or None if it was
304                 optional and not found
305
306         Raises:
307             ValueError if the symbol is invalid or not found, or references a
308                 property which is not supported
309         """
310         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
311         if not m:
312             raise ValueError("%s: Symbol '%s' has invalid format" %
313                              (msg, sym_name))
314         entry_name, prop_name = m.groups()
315         entry_name = entry_name.replace('_', '-')
316         entry = self._entries.get(entry_name)
317         if not entry:
318             if entry_name.endswith('-any'):
319                 root = entry_name[:-4]
320                 for name in self._entries:
321                     if name.startswith(root):
322                         rest = name[len(root):]
323                         if rest in ['', '-img', '-nodtb']:
324                             entry = self._entries[name]
325         if not entry:
326             err = ("%s: Entry '%s' not found in list (%s)" %
327                    (msg, entry_name, ','.join(self._entries.keys())))
328             if optional:
329                 print('Warning: %s' % err, file=sys.stderr)
330                 return None
331             raise ValueError(err)
332         if prop_name == 'offset':
333             return entry.offset
334         else:
335             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
336
337     def GetEntries(self):
338         return self._entries
339
340     def WriteMap(self, fd, indent):
341         """Write a map of the section to a .map file
342
343         Args:
344             fd: File to write the map to
345         """
346         for entry in self._entries.values():
347             entry.WriteMap(fd, indent)