blob: 5910092dae5cb56f00ec3fc9d846cd490dcee6a4 [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
Simon Glass3ab95982018-08-01 15:22:37 -060028 effectively changes the place where entry offset 0 starts
Simon Glass8f1da502018-06-01 09:38:12 -060029 _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
Simon Glass3ab95982018-08-01 15:22:37 -060032 _sort: True if entries should be sorted by offset, False if they
Simon Glass8f1da502018-06-01 09:38:12 -060033 must be in-order in the device tree description
34 _skip_at_start: Number of bytes before the first entry starts. These
Simon Glass3ab95982018-08-01 15:22:37 -060035 effectively adjust the starting offset of entries. For example,
Simon Glass8f1da502018-06-01 09:38:12 -060036 if _pad_before is 16, then the first entry would start at 16.
Simon Glass3ab95982018-08-01 15:22:37 -060037 An entry with offset = 20 would in fact be written at offset 4
Simon Glass8f1da502018-06-01 09:38:12 -060038 in the image file.
39 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
Simon Glass3ab95982018-08-01 15:22:37 -060040 used for x86 images, which want to use offsets such that a memory
41 address (like 0xff800000) is the first entry offset. This causes
42 _skip_at_start to be set to the starting memory address.
Simon Glassc8d48ef2018-06-01 09:38:21 -060043 _name_prefix: Prefix to add to the name of all entries within this
44 section
Simon Glass8f1da502018-06-01 09:38:12 -060045 _entries: OrderedDict() of entries
46 """
47 def __init__(self, name, node, test=False):
48 global entry
49 global Entry
50 import entry
51 from entry import Entry
52
Simon Glass8122f392018-07-17 13:25:28 -060053 self._name = name
Simon Glass8f1da502018-06-01 09:38:12 -060054 self._node = node
Simon Glass3ab95982018-08-01 15:22:37 -060055 self._offset = 0
Simon Glass8f1da502018-06-01 09:38:12 -060056 self._size = None
57 self._align_size = None
58 self._pad_before = 0
59 self._pad_after = 0
60 self._pad_byte = 0
61 self._sort = False
Jagdish Gediya94b57db2018-09-03 21:35:07 +053062 self._skip_at_start = None
Simon Glass8f1da502018-06-01 09:38:12 -060063 self._end_4gb = False
Simon Glassc8d48ef2018-06-01 09:38:21 -060064 self._name_prefix = ''
Simon Glass8f1da502018-06-01 09:38:12 -060065 self._entries = OrderedDict()
66 if not test:
67 self._ReadNode()
68 self._ReadEntries()
69
70 def _ReadNode(self):
71 """Read properties from the section node"""
72 self._size = fdt_util.GetInt(self._node, 'size')
73 self._align_size = fdt_util.GetInt(self._node, 'align-size')
74 if tools.NotPowerOfTwo(self._align_size):
75 self._Raise("Alignment size %s must be a power of two" %
76 self._align_size)
77 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
78 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
79 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
Simon Glass3ab95982018-08-01 15:22:37 -060080 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
Simon Glass8f1da502018-06-01 09:38:12 -060081 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
Jagdish Gediya94b57db2018-09-03 21:35:07 +053082 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
Simon Glass8f1da502018-06-01 09:38:12 -060083 if self._end_4gb:
Jagdish Gediya94b57db2018-09-03 21:35:07 +053084 if not self._size:
85 self._Raise("Section size must be provided when using end-at-4gb")
86 if self._skip_at_start is not None:
87 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
88 else:
89 self._skip_at_start = 0x100000000 - self._size
90 else:
91 if self._skip_at_start is None:
92 self._skip_at_start = 0
Simon Glassc8d48ef2018-06-01 09:38:21 -060093 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glass8f1da502018-06-01 09:38:12 -060094
95 def _ReadEntries(self):
96 for node in self._node.subnodes:
Simon Glassc8d48ef2018-06-01 09:38:21 -060097 entry = Entry.Create(self, node)
98 entry.SetPrefix(self._name_prefix)
99 self._entries[node.name] = entry
Simon Glass8f1da502018-06-01 09:38:12 -0600100
Simon Glass8122f392018-07-17 13:25:28 -0600101 def SetOffset(self, offset):
102 self._offset = offset
103
Simon Glass078ab1a2018-07-06 10:27:41 -0600104 def AddMissingProperties(self):
Simon Glass8122f392018-07-17 13:25:28 -0600105 """Add new properties to the device tree as needed for this entry"""
Simon Glassdbf6be92018-08-01 15:22:42 -0600106 for prop in ['offset', 'size', 'image-pos']:
Simon Glass8122f392018-07-17 13:25:28 -0600107 if not prop in self._node.props:
108 self._node.AddZeroProp(prop)
Simon Glass078ab1a2018-07-06 10:27:41 -0600109 for entry in self._entries.values():
110 entry.AddMissingProperties()
111
112 def SetCalculatedProperties(self):
Simon Glass8122f392018-07-17 13:25:28 -0600113 self._node.SetInt('offset', self._offset)
114 self._node.SetInt('size', self._size)
Simon Glassdbf6be92018-08-01 15:22:42 -0600115 self._node.SetInt('image-pos', self._image_pos)
Simon Glass078ab1a2018-07-06 10:27:41 -0600116 for entry in self._entries.values():
117 entry.SetCalculatedProperties()
118
Simon Glassecab8972018-07-06 10:27:40 -0600119 def ProcessFdt(self, fdt):
120 todo = self._entries.values()
121 for passnum in range(3):
122 next_todo = []
123 for entry in todo:
124 if not entry.ProcessFdt(fdt):
125 next_todo.append(entry)
126 todo = next_todo
127 if not todo:
128 break
129 if todo:
130 self._Raise('Internal error: Could not complete processing of Fdt: '
131 'remaining %s' % todo)
132 return True
133
Simon Glass8f1da502018-06-01 09:38:12 -0600134 def CheckSize(self):
135 """Check that the section contents does not exceed its size, etc."""
136 contents_size = 0
137 for entry in self._entries.values():
Simon Glass3ab95982018-08-01 15:22:37 -0600138 contents_size = max(contents_size, entry.offset + entry.size)
Simon Glass8f1da502018-06-01 09:38:12 -0600139
140 contents_size -= self._skip_at_start
141
142 size = self._size
143 if not size:
144 size = self._pad_before + contents_size + self._pad_after
145 size = tools.Align(size, self._align_size)
146
147 if self._size and contents_size > self._size:
148 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
149 (contents_size, contents_size, self._size, self._size))
150 if not self._size:
151 self._size = size
152 if self._size != tools.Align(self._size, self._align_size):
153 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
154 (self._size, self._size, self._align_size, self._align_size))
155 return size
156
157 def _Raise(self, msg):
158 """Raises an error for this section
159
160 Args:
161 msg: Error message to use in the raise string
162 Raises:
163 ValueError()
164 """
165 raise ValueError("Section '%s': %s" % (self._node.path, msg))
166
167 def GetPath(self):
168 """Get the path of an image (in the FDT)
169
170 Returns:
171 Full path of the node for this image
172 """
173 return self._node.path
174
175 def FindEntryType(self, etype):
176 """Find an entry type in the section
177
178 Args:
179 etype: Entry type to find
180 Returns:
181 entry matching that type, or None if not found
182 """
183 for entry in self._entries.values():
184 if entry.etype == etype:
185 return entry
186 return None
187
188 def GetEntryContents(self):
189 """Call ObtainContents() for each entry
190
191 This calls each entry's ObtainContents() a few times until they all
192 return True. We stop calling an entry's function once it returns
193 True. This allows the contents of one entry to depend on another.
194
195 After 3 rounds we give up since it's likely an error.
196 """
197 todo = self._entries.values()
198 for passnum in range(3):
199 next_todo = []
200 for entry in todo:
201 if not entry.ObtainContents():
202 next_todo.append(entry)
203 todo = next_todo
204 if not todo:
205 break
Simon Glass736bb0a2018-07-06 10:27:17 -0600206 if todo:
207 self._Raise('Internal error: Could not complete processing of '
208 'contents: remaining %s' % todo)
209 return True
Simon Glass8f1da502018-06-01 09:38:12 -0600210
Simon Glass3ab95982018-08-01 15:22:37 -0600211 def _SetEntryOffsetSize(self, name, offset, size):
212 """Set the offset and size of an entry
Simon Glass8f1da502018-06-01 09:38:12 -0600213
214 Args:
215 name: Entry name to update
Simon Glass3ab95982018-08-01 15:22:37 -0600216 offset: New offset
Simon Glass8f1da502018-06-01 09:38:12 -0600217 size: New size
218 """
219 entry = self._entries.get(name)
220 if not entry:
Simon Glass3ab95982018-08-01 15:22:37 -0600221 self._Raise("Unable to set offset/size for unknown entry '%s'" %
222 name)
223 entry.SetOffsetSize(self._skip_at_start + offset, size)
Simon Glass8f1da502018-06-01 09:38:12 -0600224
Simon Glass3ab95982018-08-01 15:22:37 -0600225 def GetEntryOffsets(self):
226 """Handle entries that want to set the offset/size of other entries
Simon Glass8f1da502018-06-01 09:38:12 -0600227
Simon Glass3ab95982018-08-01 15:22:37 -0600228 This calls each entry's GetOffsets() method. If it returns a list
Simon Glass8f1da502018-06-01 09:38:12 -0600229 of entries to update, it updates them.
230 """
231 for entry in self._entries.values():
Simon Glass3ab95982018-08-01 15:22:37 -0600232 offset_dict = entry.GetOffsets()
233 for name, info in offset_dict.iteritems():
234 self._SetEntryOffsetSize(name, *info)
Simon Glass8f1da502018-06-01 09:38:12 -0600235
236 def PackEntries(self):
237 """Pack all entries into the section"""
Simon Glass3ab95982018-08-01 15:22:37 -0600238 offset = self._skip_at_start
Simon Glass8f1da502018-06-01 09:38:12 -0600239 for entry in self._entries.values():
Simon Glass3ab95982018-08-01 15:22:37 -0600240 offset = entry.Pack(offset)
241 self._size = self.CheckSize()
Simon Glass8f1da502018-06-01 09:38:12 -0600242
243 def _SortEntries(self):
Simon Glass3ab95982018-08-01 15:22:37 -0600244 """Sort entries by offset"""
245 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
Simon Glass8f1da502018-06-01 09:38:12 -0600246 self._entries.clear()
247 for entry in entries:
248 self._entries[entry._node.name] = entry
249
250 def CheckEntries(self):
251 """Check that entries do not overlap or extend outside the section"""
252 if self._sort:
253 self._SortEntries()
Simon Glass3ab95982018-08-01 15:22:37 -0600254 offset = 0
Simon Glass8f1da502018-06-01 09:38:12 -0600255 prev_name = 'None'
256 for entry in self._entries.values():
Simon Glass3ab95982018-08-01 15:22:37 -0600257 entry.CheckOffset()
258 if (entry.offset < self._skip_at_start or
259 entry.offset >= self._skip_at_start + self._size):
260 entry.Raise("Offset %#x (%d) is outside the section starting "
Simon Glass8f1da502018-06-01 09:38:12 -0600261 "at %#x (%d)" %
Simon Glass3ab95982018-08-01 15:22:37 -0600262 (entry.offset, entry.offset, self._skip_at_start,
Simon Glass8f1da502018-06-01 09:38:12 -0600263 self._skip_at_start))
Simon Glass3ab95982018-08-01 15:22:37 -0600264 if entry.offset < offset:
265 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
Simon Glass8f1da502018-06-01 09:38:12 -0600266 "ending at %#x (%d)" %
Simon Glass3ab95982018-08-01 15:22:37 -0600267 (entry.offset, entry.offset, prev_name, offset, offset))
268 offset = entry.offset + entry.size
Simon Glass8f1da502018-06-01 09:38:12 -0600269 prev_name = entry.GetPath()
270
Simon Glassdbf6be92018-08-01 15:22:42 -0600271 def SetImagePos(self, image_pos):
272 self._image_pos = image_pos
273 for entry in self._entries.values():
274 entry.SetImagePos(image_pos)
275
Simon Glass8f1da502018-06-01 09:38:12 -0600276 def ProcessEntryContents(self):
277 """Call the ProcessContents() method for each entry
278
279 This is intended to adjust the contents as needed by the entry type.
280 """
281 for entry in self._entries.values():
282 entry.ProcessContents()
283
284 def WriteSymbols(self):
285 """Write symbol values into binary files for access at run time"""
286 for entry in self._entries.values():
287 entry.WriteSymbols(self)
288
Simon Glass3ab95982018-08-01 15:22:37 -0600289 def BuildSection(self, fd, base_offset):
Simon Glass8f1da502018-06-01 09:38:12 -0600290 """Write the section to a file"""
Simon Glass3ab95982018-08-01 15:22:37 -0600291 fd.seek(base_offset)
Simon Glass8f1da502018-06-01 09:38:12 -0600292 fd.write(self.GetData())
293
294 def GetData(self):
Simon Glass8122f392018-07-17 13:25:28 -0600295 """Get the contents of the section"""
Simon Glass8f1da502018-06-01 09:38:12 -0600296 section_data = chr(self._pad_byte) * self._size
297
298 for entry in self._entries.values():
299 data = entry.GetData()
Simon Glass3ab95982018-08-01 15:22:37 -0600300 base = self._pad_before + entry.offset - self._skip_at_start
Simon Glass8f1da502018-06-01 09:38:12 -0600301 section_data = (section_data[:base] + data +
302 section_data[base + len(data):])
303 return section_data
304
305 def LookupSymbol(self, sym_name, optional, msg):
306 """Look up a symbol in an ELF file
307
308 Looks up a symbol in an ELF file. Only entry types which come from an
309 ELF image can be used by this function.
310
Simon Glass3ab95982018-08-01 15:22:37 -0600311 At present the only entry property supported is offset.
Simon Glass8f1da502018-06-01 09:38:12 -0600312
313 Args:
314 sym_name: Symbol name in the ELF file to look up in the format
315 _binman_<entry>_prop_<property> where <entry> is the name of
316 the entry and <property> is the property to find (e.g.
Simon Glass3ab95982018-08-01 15:22:37 -0600317 _binman_u_boot_prop_offset). As a special case, you can append
Simon Glass8f1da502018-06-01 09:38:12 -0600318 _any to <entry> to have it search for any matching entry. E.g.
Simon Glass3ab95982018-08-01 15:22:37 -0600319 _binman_u_boot_any_prop_offset will match entries called u-boot,
Simon Glass8f1da502018-06-01 09:38:12 -0600320 u-boot-img and u-boot-nodtb)
321 optional: True if the symbol is optional. If False this function
322 will raise if the symbol is not found
323 msg: Message to display if an error occurs
324
325 Returns:
326 Value that should be assigned to that symbol, or None if it was
327 optional and not found
328
329 Raises:
330 ValueError if the symbol is invalid or not found, or references a
331 property which is not supported
332 """
333 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
334 if not m:
335 raise ValueError("%s: Symbol '%s' has invalid format" %
336 (msg, sym_name))
337 entry_name, prop_name = m.groups()
338 entry_name = entry_name.replace('_', '-')
339 entry = self._entries.get(entry_name)
340 if not entry:
341 if entry_name.endswith('-any'):
342 root = entry_name[:-4]
343 for name in self._entries:
344 if name.startswith(root):
345 rest = name[len(root):]
346 if rest in ['', '-img', '-nodtb']:
347 entry = self._entries[name]
348 if not entry:
349 err = ("%s: Entry '%s' not found in list (%s)" %
350 (msg, entry_name, ','.join(self._entries.keys())))
351 if optional:
352 print('Warning: %s' % err, file=sys.stderr)
353 return None
354 raise ValueError(err)
Simon Glass3ab95982018-08-01 15:22:37 -0600355 if prop_name == 'offset':
356 return entry.offset
Simon Glassdbf6be92018-08-01 15:22:42 -0600357 elif prop_name == 'image_pos':
358 return entry.image_pos
Simon Glass8f1da502018-06-01 09:38:12 -0600359 else:
360 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
361
362 def GetEntries(self):
Simon Glass8122f392018-07-17 13:25:28 -0600363 """Get the number of entries in a section
364
365 Returns:
366 Number of entries in a section
367 """
Simon Glass8f1da502018-06-01 09:38:12 -0600368 return self._entries
Simon Glass3b0c3822018-06-01 09:38:20 -0600369
Simon Glass8122f392018-07-17 13:25:28 -0600370 def GetSize(self):
371 """Get the size of a section in bytes
372
373 This is only meaningful if the section has a pre-defined size, or the
374 entries within it have been packed, so that the size has been
375 calculated.
376
377 Returns:
378 Entry size in bytes
379 """
380 return self._size
381
Simon Glass3b0c3822018-06-01 09:38:20 -0600382 def WriteMap(self, fd, indent):
383 """Write a map of the section to a .map file
384
385 Args:
386 fd: File to write the map to
387 """
Simon Glass1be70d22018-07-17 13:25:49 -0600388 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
389 self._image_pos)
Simon Glass3b0c3822018-06-01 09:38:20 -0600390 for entry in self._entries.values():
Simon Glass8122f392018-07-17 13:25:28 -0600391 entry.WriteMap(fd, indent + 1)
Simon Glass24d0d3c2018-07-17 13:25:47 -0600392
393 def GetContentsByPhandle(self, phandle, source_entry):
394 """Get the data contents of an entry specified by a phandle
395
396 This uses a phandle to look up a node and and find the entry
397 associated with it. Then it returnst he contents of that entry.
398
399 Args:
400 phandle: Phandle to look up (integer)
401 source_entry: Entry containing that phandle (used for error
402 reporting)
403
404 Returns:
405 data from associated entry (as a string), or None if not found
406 """
407 node = self._node.GetFdt().LookupPhandle(phandle)
408 if not node:
409 source_entry.Raise("Cannot find node for phandle %d" % phandle)
410 for entry in self._entries.values():
411 if entry._node == node:
412 if entry.data is None:
413 return None
414 return entry.data
415 source_entry.Raise("Cannot find entry for node '%s'" % node.name)