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