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