]> git.sur5r.net Git - u-boot/blob - tools/binman/bsection.py
binman: Add support for adding a name prefix to entries
[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
166     def _SetEntryPosSize(self, name, pos, size):
167         """Set the position and size of an entry
168
169         Args:
170             name: Entry name to update
171             pos: New position
172             size: New size
173         """
174         entry = self._entries.get(name)
175         if not entry:
176             self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
177         entry.SetPositionSize(self._skip_at_start + pos, size)
178
179     def GetEntryPositions(self):
180         """Handle entries that want to set the position/size of other entries
181
182         This calls each entry's GetPositions() method. If it returns a list
183         of entries to update, it updates them.
184         """
185         for entry in self._entries.values():
186             pos_dict = entry.GetPositions()
187             for name, info in pos_dict.iteritems():
188                 self._SetEntryPosSize(name, *info)
189
190     def PackEntries(self):
191         """Pack all entries into the section"""
192         pos = self._skip_at_start
193         for entry in self._entries.values():
194             pos = entry.Pack(pos)
195
196     def _SortEntries(self):
197         """Sort entries by position"""
198         entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
199         self._entries.clear()
200         for entry in entries:
201             self._entries[entry._node.name] = entry
202
203     def CheckEntries(self):
204         """Check that entries do not overlap or extend outside the section"""
205         if self._sort:
206             self._SortEntries()
207         pos = 0
208         prev_name = 'None'
209         for entry in self._entries.values():
210             entry.CheckPosition()
211             if (entry.pos < self._skip_at_start or
212                 entry.pos >= self._skip_at_start + self._size):
213                 entry.Raise("Position %#x (%d) is outside the section starting "
214                             "at %#x (%d)" %
215                             (entry.pos, entry.pos, self._skip_at_start,
216                              self._skip_at_start))
217             if entry.pos < pos:
218                 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
219                             "ending at %#x (%d)" %
220                             (entry.pos, entry.pos, prev_name, pos, pos))
221             pos = entry.pos + entry.size
222             prev_name = entry.GetPath()
223
224     def ProcessEntryContents(self):
225         """Call the ProcessContents() method for each entry
226
227         This is intended to adjust the contents as needed by the entry type.
228         """
229         for entry in self._entries.values():
230             entry.ProcessContents()
231
232     def WriteSymbols(self):
233         """Write symbol values into binary files for access at run time"""
234         for entry in self._entries.values():
235             entry.WriteSymbols(self)
236
237     def BuildSection(self, fd, base_pos):
238         """Write the section to a file"""
239         fd.seek(base_pos)
240         fd.write(self.GetData())
241
242     def GetData(self):
243         """Write the section to a file"""
244         section_data = chr(self._pad_byte) * self._size
245
246         for entry in self._entries.values():
247             data = entry.GetData()
248             base = self._pad_before + entry.pos - self._skip_at_start
249             section_data = (section_data[:base] + data +
250                             section_data[base + len(data):])
251         return section_data
252
253     def LookupSymbol(self, sym_name, optional, msg):
254         """Look up a symbol in an ELF file
255
256         Looks up a symbol in an ELF file. Only entry types which come from an
257         ELF image can be used by this function.
258
259         At present the only entry property supported is pos.
260
261         Args:
262             sym_name: Symbol name in the ELF file to look up in the format
263                 _binman_<entry>_prop_<property> where <entry> is the name of
264                 the entry and <property> is the property to find (e.g.
265                 _binman_u_boot_prop_pos). As a special case, you can append
266                 _any to <entry> to have it search for any matching entry. E.g.
267                 _binman_u_boot_any_prop_pos will match entries called u-boot,
268                 u-boot-img and u-boot-nodtb)
269             optional: True if the symbol is optional. If False this function
270                 will raise if the symbol is not found
271             msg: Message to display if an error occurs
272
273         Returns:
274             Value that should be assigned to that symbol, or None if it was
275                 optional and not found
276
277         Raises:
278             ValueError if the symbol is invalid or not found, or references a
279                 property which is not supported
280         """
281         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
282         if not m:
283             raise ValueError("%s: Symbol '%s' has invalid format" %
284                              (msg, sym_name))
285         entry_name, prop_name = m.groups()
286         entry_name = entry_name.replace('_', '-')
287         entry = self._entries.get(entry_name)
288         if not entry:
289             if entry_name.endswith('-any'):
290                 root = entry_name[:-4]
291                 for name in self._entries:
292                     if name.startswith(root):
293                         rest = name[len(root):]
294                         if rest in ['', '-img', '-nodtb']:
295                             entry = self._entries[name]
296         if not entry:
297             err = ("%s: Entry '%s' not found in list (%s)" %
298                    (msg, entry_name, ','.join(self._entries.keys())))
299             if optional:
300                 print('Warning: %s' % err, file=sys.stderr)
301                 return None
302             raise ValueError(err)
303         if prop_name == 'pos':
304             return entry.pos
305         else:
306             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
307
308     def GetEntries(self):
309         return self._entries
310
311     def WriteMap(self, fd, indent):
312         """Write a map of the section to a .map file
313
314         Args:
315             fd: File to write the map to
316         """
317         for entry in self._entries.values():
318             entry.WriteMap(fd, indent)