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