1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Base class for sections (collections of entries)
8 from __future__ import print_function
10 from collections import OrderedDict
19 class Section(object):
20 """A section which contains multiple entries
22 A section represents a collection of entries. There must be one or more
23 sections in an image. Sections are used to group entries together.
26 _node: Node object that contains the section definition in device tree
27 _size: Section size in bytes, or None if not known yet
28 _align_size: Section size alignment, or None
29 _pad_before: Number of bytes before the first entry starts. This
30 effectively changes the place where entry offset 0 starts
31 _pad_after: Number of bytes after the last entry ends. The last
32 entry will finish on or before this boundary
33 _pad_byte: Byte to use to pad the section where there is no entry
34 _sort: True if entries should be sorted by offset, False if they
35 must be in-order in the device tree description
36 _skip_at_start: Number of bytes before the first entry starts. These
37 effectively adjust the starting offset of entries. For example,
38 if _pad_before is 16, then the first entry would start at 16.
39 An entry with offset = 20 would in fact be written at offset 4
41 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
42 used for x86 images, which want to use offsets such that a memory
43 address (like 0xff800000) is the first entry offset. This causes
44 _skip_at_start to be set to the starting memory address.
45 _name_prefix: Prefix to add to the name of all entries within this
47 _entries: OrderedDict() of entries
49 def __init__(self, name, node, test=False):
53 from entry import Entry
59 self._align_size = None
64 self._skip_at_start = 0
66 self._name_prefix = ''
67 self._entries = OrderedDict()
73 """Read properties from the section node"""
74 self._size = fdt_util.GetInt(self._node, 'size')
75 self._align_size = fdt_util.GetInt(self._node, 'align-size')
76 if tools.NotPowerOfTwo(self._align_size):
77 self._Raise("Alignment size %s must be a power of two" %
79 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
80 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
81 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
82 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
83 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
84 if self._end_4gb and not self._size:
85 self._Raise("Section size must be provided when using end-at-4gb")
87 self._skip_at_start = 0x100000000 - self._size
88 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
90 def _ReadEntries(self):
91 for node in self._node.subnodes:
92 if node.name == 'hash':
94 entry = Entry.Create(self, node)
95 entry.SetPrefix(self._name_prefix)
96 self._entries[node.name] = entry
99 """Get the set of device tree files used by this image"""
101 for entry in self._entries.values():
102 fdt_set.update(entry.GetFdtSet())
105 def SetOffset(self, offset):
106 self._offset = offset
108 def ExpandEntries(self):
109 for entry in self._entries.values():
110 entry.ExpandEntries()
112 def AddMissingProperties(self):
113 """Add new properties to the device tree as needed for this entry"""
114 for prop in ['offset', 'size', 'image-pos']:
115 if not prop in self._node.props:
116 state.AddZeroProp(self._node, prop)
117 state.CheckAddHashProp(self._node)
118 for entry in self._entries.values():
119 entry.AddMissingProperties()
121 def SetCalculatedProperties(self):
122 state.SetInt(self._node, 'offset', self._offset)
123 state.SetInt(self._node, 'size', self._size)
124 state.SetInt(self._node, 'image-pos', self._image_pos)
125 for entry in self._entries.values():
126 entry.SetCalculatedProperties()
128 def ProcessFdt(self, fdt):
129 todo = self._entries.values()
130 for passnum in range(3):
133 if not entry.ProcessFdt(fdt):
134 next_todo.append(entry)
139 self._Raise('Internal error: Could not complete processing of Fdt: '
140 'remaining %s' % todo)
144 """Check that the section contents does not exceed its size, etc."""
146 for entry in self._entries.values():
147 contents_size = max(contents_size, entry.offset + entry.size)
149 contents_size -= self._skip_at_start
153 size = self._pad_before + contents_size + self._pad_after
154 size = tools.Align(size, self._align_size)
156 if self._size and contents_size > self._size:
157 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
158 (contents_size, contents_size, self._size, self._size))
161 if self._size != tools.Align(self._size, self._align_size):
162 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
163 (self._size, self._size, self._align_size, self._align_size))
166 def _Raise(self, msg):
167 """Raises an error for this section
170 msg: Error message to use in the raise string
174 raise ValueError("Section '%s': %s" % (self._node.path, msg))
177 """Get the path of an image (in the FDT)
180 Full path of the node for this image
182 return self._node.path
184 def FindEntryType(self, etype):
185 """Find an entry type in the section
188 etype: Entry type to find
190 entry matching that type, or None if not found
192 for entry in self._entries.values():
193 if entry.etype == etype:
197 def GetEntryContents(self):
198 """Call ObtainContents() for each entry
200 This calls each entry's ObtainContents() a few times until they all
201 return True. We stop calling an entry's function once it returns
202 True. This allows the contents of one entry to depend on another.
204 After 3 rounds we give up since it's likely an error.
206 todo = self._entries.values()
207 for passnum in range(3):
210 if not entry.ObtainContents():
211 next_todo.append(entry)
216 self._Raise('Internal error: Could not complete processing of '
217 'contents: remaining %s' % todo)
220 def _SetEntryOffsetSize(self, name, offset, size):
221 """Set the offset and size of an entry
224 name: Entry name to update
228 entry = self._entries.get(name)
230 self._Raise("Unable to set offset/size for unknown entry '%s'" %
232 entry.SetOffsetSize(self._skip_at_start + offset, size)
234 def GetEntryOffsets(self):
235 """Handle entries that want to set the offset/size of other entries
237 This calls each entry's GetOffsets() method. If it returns a list
238 of entries to update, it updates them.
240 for entry in self._entries.values():
241 offset_dict = entry.GetOffsets()
242 for name, info in offset_dict.iteritems():
243 self._SetEntryOffsetSize(name, *info)
245 def PackEntries(self):
246 """Pack all entries into the section"""
247 offset = self._skip_at_start
248 for entry in self._entries.values():
249 offset = entry.Pack(offset)
250 self._size = self.CheckSize()
252 def _SortEntries(self):
253 """Sort entries by offset"""
254 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
255 self._entries.clear()
256 for entry in entries:
257 self._entries[entry._node.name] = entry
259 def _ExpandEntries(self):
260 """Expand any entries that are permitted to"""
262 for entry in self._entries.values():
264 exp_entry.ExpandToLimit(entry.offset)
266 if entry.expand_size:
269 exp_entry.ExpandToLimit(self._size)
271 def CheckEntries(self):
272 """Check that entries do not overlap or extend outside the section
274 This also sorts entries, if needed and expands
278 self._ExpandEntries()
281 for entry in self._entries.values():
283 if (entry.offset < self._skip_at_start or
284 entry.offset + entry.size > self._skip_at_start + self._size):
285 entry.Raise("Offset %#x (%d) is outside the section starting "
287 (entry.offset, entry.offset, self._skip_at_start,
288 self._skip_at_start))
289 if entry.offset < offset:
290 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
291 "ending at %#x (%d)" %
292 (entry.offset, entry.offset, prev_name, offset, offset))
293 offset = entry.offset + entry.size
294 prev_name = entry.GetPath()
296 def SetImagePos(self, image_pos):
297 self._image_pos = image_pos
298 for entry in self._entries.values():
299 entry.SetImagePos(image_pos)
301 def ProcessEntryContents(self):
302 """Call the ProcessContents() method for each entry
304 This is intended to adjust the contents as needed by the entry type.
306 for entry in self._entries.values():
307 entry.ProcessContents()
309 def WriteSymbols(self):
310 """Write symbol values into binary files for access at run time"""
311 for entry in self._entries.values():
312 entry.WriteSymbols(self)
314 def BuildSection(self, fd, base_offset):
315 """Write the section to a file"""
317 fd.write(self.GetData())
320 """Get the contents of the section"""
321 section_data = chr(self._pad_byte) * self._size
323 for entry in self._entries.values():
324 data = entry.GetData()
325 base = self._pad_before + entry.offset - self._skip_at_start
326 section_data = (section_data[:base] + data +
327 section_data[base + len(data):])
330 def LookupSymbol(self, sym_name, optional, msg):
331 """Look up a symbol in an ELF file
333 Looks up a symbol in an ELF file. Only entry types which come from an
334 ELF image can be used by this function.
336 At present the only entry property supported is offset.
339 sym_name: Symbol name in the ELF file to look up in the format
340 _binman_<entry>_prop_<property> where <entry> is the name of
341 the entry and <property> is the property to find (e.g.
342 _binman_u_boot_prop_offset). As a special case, you can append
343 _any to <entry> to have it search for any matching entry. E.g.
344 _binman_u_boot_any_prop_offset will match entries called u-boot,
345 u-boot-img and u-boot-nodtb)
346 optional: True if the symbol is optional. If False this function
347 will raise if the symbol is not found
348 msg: Message to display if an error occurs
351 Value that should be assigned to that symbol, or None if it was
352 optional and not found
355 ValueError if the symbol is invalid or not found, or references a
356 property which is not supported
358 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
360 raise ValueError("%s: Symbol '%s' has invalid format" %
362 entry_name, prop_name = m.groups()
363 entry_name = entry_name.replace('_', '-')
364 entry = self._entries.get(entry_name)
366 if entry_name.endswith('-any'):
367 root = entry_name[:-4]
368 for name in self._entries:
369 if name.startswith(root):
370 rest = name[len(root):]
371 if rest in ['', '-img', '-nodtb']:
372 entry = self._entries[name]
374 err = ("%s: Entry '%s' not found in list (%s)" %
375 (msg, entry_name, ','.join(self._entries.keys())))
377 print('Warning: %s' % err, file=sys.stderr)
379 raise ValueError(err)
380 if prop_name == 'offset':
382 elif prop_name == 'image_pos':
383 return entry.image_pos
385 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
387 def GetEntries(self):
388 """Get the number of entries in a section
391 Number of entries in a section
396 """Get the size of a section in bytes
398 This is only meaningful if the section has a pre-defined size, or the
399 entries within it have been packed, so that the size has been
407 def WriteMap(self, fd, indent):
408 """Write a map of the section to a .map file
411 fd: File to write the map to
413 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
415 for entry in self._entries.values():
416 entry.WriteMap(fd, indent + 1)
418 def GetContentsByPhandle(self, phandle, source_entry):
419 """Get the data contents of an entry specified by a phandle
421 This uses a phandle to look up a node and and find the entry
422 associated with it. Then it returnst he contents of that entry.
425 phandle: Phandle to look up (integer)
426 source_entry: Entry containing that phandle (used for error
430 data from associated entry (as a string), or None if not found
432 node = self._node.GetFdt().LookupPhandle(phandle)
434 source_entry.Raise("Cannot find node for phandle %d" % phandle)
435 for entry in self._entries.values():
436 if entry._node == node:
437 if entry.data is None:
440 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
442 def ExpandSize(self, size):
443 if size != self._size: