]> git.sur5r.net Git - u-boot/blob - tools/binman/bsection.py
de439ef625fcb894f547e034be8510545aa60d40
[u-boot] / 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 position 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 position, 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 position of entries. For example,
36             if _pad_before is 16, then the first entry would start at 16.
37             An entry with pos = 20 would in fact be written at position 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 positions such that a
41              memory address (like 0xff800000) is the first entry position.
42              This causes _skip_at_start to be set to the starting memory
43              address.
44         _name_prefix: Prefix to add to the name of all entries within this
45             section
46         _entries: OrderedDict() of entries
47     """
48     def __init__(self, name, node, test=False):
49         global entry
50         global Entry
51         import entry
52         from entry import Entry
53
54         self._node = node
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-pos')
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.pos + 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 _SetEntryPosSize(self, name, pos, size):
194         """Set the position and size of an entry
195
196         Args:
197             name: Entry name to update
198             pos: New position
199             size: New size
200         """
201         entry = self._entries.get(name)
202         if not entry:
203             self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
204         entry.SetPositionSize(self._skip_at_start + pos, size)
205
206     def GetEntryPositions(self):
207         """Handle entries that want to set the position/size of other entries
208
209         This calls each entry's GetPositions() method. If it returns a list
210         of entries to update, it updates them.
211         """
212         for entry in self._entries.values():
213             pos_dict = entry.GetPositions()
214             for name, info in pos_dict.iteritems():
215                 self._SetEntryPosSize(name, *info)
216
217     def PackEntries(self):
218         """Pack all entries into the section"""
219         pos = self._skip_at_start
220         for entry in self._entries.values():
221             pos = entry.Pack(pos)
222
223     def _SortEntries(self):
224         """Sort entries by position"""
225         entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
226         self._entries.clear()
227         for entry in entries:
228             self._entries[entry._node.name] = entry
229
230     def CheckEntries(self):
231         """Check that entries do not overlap or extend outside the section"""
232         if self._sort:
233             self._SortEntries()
234         pos = 0
235         prev_name = 'None'
236         for entry in self._entries.values():
237             entry.CheckPosition()
238             if (entry.pos < self._skip_at_start or
239                 entry.pos >= self._skip_at_start + self._size):
240                 entry.Raise("Position %#x (%d) is outside the section starting "
241                             "at %#x (%d)" %
242                             (entry.pos, entry.pos, self._skip_at_start,
243                              self._skip_at_start))
244             if entry.pos < pos:
245                 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
246                             "ending at %#x (%d)" %
247                             (entry.pos, entry.pos, prev_name, pos, pos))
248             pos = entry.pos + entry.size
249             prev_name = entry.GetPath()
250
251     def ProcessEntryContents(self):
252         """Call the ProcessContents() method for each entry
253
254         This is intended to adjust the contents as needed by the entry type.
255         """
256         for entry in self._entries.values():
257             entry.ProcessContents()
258
259     def WriteSymbols(self):
260         """Write symbol values into binary files for access at run time"""
261         for entry in self._entries.values():
262             entry.WriteSymbols(self)
263
264     def BuildSection(self, fd, base_pos):
265         """Write the section to a file"""
266         fd.seek(base_pos)
267         fd.write(self.GetData())
268
269     def GetData(self):
270         """Write the section to a file"""
271         section_data = chr(self._pad_byte) * self._size
272
273         for entry in self._entries.values():
274             data = entry.GetData()
275             base = self._pad_before + entry.pos - self._skip_at_start
276             section_data = (section_data[:base] + data +
277                             section_data[base + len(data):])
278         return section_data
279
280     def LookupSymbol(self, sym_name, optional, msg):
281         """Look up a symbol in an ELF file
282
283         Looks up a symbol in an ELF file. Only entry types which come from an
284         ELF image can be used by this function.
285
286         At present the only entry property supported is pos.
287
288         Args:
289             sym_name: Symbol name in the ELF file to look up in the format
290                 _binman_<entry>_prop_<property> where <entry> is the name of
291                 the entry and <property> is the property to find (e.g.
292                 _binman_u_boot_prop_pos). As a special case, you can append
293                 _any to <entry> to have it search for any matching entry. E.g.
294                 _binman_u_boot_any_prop_pos will match entries called u-boot,
295                 u-boot-img and u-boot-nodtb)
296             optional: True if the symbol is optional. If False this function
297                 will raise if the symbol is not found
298             msg: Message to display if an error occurs
299
300         Returns:
301             Value that should be assigned to that symbol, or None if it was
302                 optional and not found
303
304         Raises:
305             ValueError if the symbol is invalid or not found, or references a
306                 property which is not supported
307         """
308         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
309         if not m:
310             raise ValueError("%s: Symbol '%s' has invalid format" %
311                              (msg, sym_name))
312         entry_name, prop_name = m.groups()
313         entry_name = entry_name.replace('_', '-')
314         entry = self._entries.get(entry_name)
315         if not entry:
316             if entry_name.endswith('-any'):
317                 root = entry_name[:-4]
318                 for name in self._entries:
319                     if name.startswith(root):
320                         rest = name[len(root):]
321                         if rest in ['', '-img', '-nodtb']:
322                             entry = self._entries[name]
323         if not entry:
324             err = ("%s: Entry '%s' not found in list (%s)" %
325                    (msg, entry_name, ','.join(self._entries.keys())))
326             if optional:
327                 print('Warning: %s' % err, file=sys.stderr)
328                 return None
329             raise ValueError(err)
330         if prop_name == 'pos':
331             return entry.pos
332         else:
333             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
334
335     def GetEntries(self):
336         return self._entries
337
338     def WriteMap(self, fd, indent):
339         """Write a map of the section to a .map file
340
341         Args:
342             fd: File to write the map to
343         """
344         for entry in self._entries.values():
345             entry.WriteMap(fd, indent)