blob: 3ed361d69a245e31d0155080dcb786e041945756 [file] [log] [blame]
Simon Glass8f1da502018-06-01 09:38:12 -06001# 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
8from __future__ import print_function
9
10from collections import OrderedDict
11import sys
12
13import fdt_util
14import re
15import tools
16
17class 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.
Simon Glassc8d48ef2018-06-01 09:38:21 -060044 _name_prefix: Prefix to add to the name of all entries within this
45 section
Simon Glass8f1da502018-06-01 09:38:12 -060046 _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
Simon Glassc8d48ef2018-06-01 09:38:21 -060063 self._name_prefix = ''
Simon Glass8f1da502018-06-01 09:38:12 -060064 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
Simon Glassc8d48ef2018-06-01 09:38:21 -060085 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glass8f1da502018-06-01 09:38:12 -060086
87 def _ReadEntries(self):
88 for node in self._node.subnodes:
Simon Glassc8d48ef2018-06-01 09:38:21 -060089 entry = Entry.Create(self, node)
90 entry.SetPrefix(self._name_prefix)
91 self._entries[node.name] = entry
Simon Glass8f1da502018-06-01 09:38:12 -060092
Simon Glassecab8972018-07-06 10:27:40 -060093 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
Simon Glass8f1da502018-06-01 09:38:12 -0600108 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
Simon Glass736bb0a2018-07-06 10:27:17 -0600180 if todo:
181 self._Raise('Internal error: Could not complete processing of '
182 'contents: remaining %s' % todo)
183 return True
Simon Glass8f1da502018-06-01 09:38:12 -0600184
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():
Simon Glass18546952018-06-01 09:38:16 -0600229 entry.CheckPosition()
Simon Glass8f1da502018-06-01 09:38:12 -0600230 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
Simon Glass3b0c3822018-06-01 09:38:20 -0600329
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)