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 _parent_section: Parent Section object which created this Section
28 _size: Section size in bytes, or None if not known yet
29 _align_size: Section size alignment, or None
30 _pad_before: Number of bytes before the first entry starts. This
31 effectively changes the place where entry offset 0 starts
32 _pad_after: Number of bytes after the last entry ends. The last
33 entry will finish on or before this boundary
34 _pad_byte: Byte to use to pad the section where there is no entry
35 _sort: True if entries should be sorted by offset, False if they
36 must be in-order in the device tree description
37 _skip_at_start: Number of bytes before the first entry starts. These
38 effectively adjust the starting offset of entries. For example,
39 if _pad_before is 16, then the first entry would start at 16.
40 An entry with offset = 20 would in fact be written at offset 4
42 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
43 used for x86 images, which want to use offsets such that a memory
44 address (like 0xff800000) is the first entry offset. This causes
45 _skip_at_start to be set to the starting memory address.
46 _name_prefix: Prefix to add to the name of all entries within this
48 _entries: OrderedDict() of entries
50 def __init__(self, name, parent_section, node, image, test=False):
54 from entry import Entry
56 self._parent_section = parent_section
62 self._align_size = None
67 self._skip_at_start = None
69 self._name_prefix = ''
70 self._entries = OrderedDict()
71 self._image_pos = None
77 """Read properties from the section node"""
78 self._offset = fdt_util.GetInt(self._node, 'offset')
79 self._size = fdt_util.GetInt(self._node, 'size')
80 self._align_size = fdt_util.GetInt(self._node, 'align-size')
81 if tools.NotPowerOfTwo(self._align_size):
82 self._Raise("Alignment size %s must be a power of two" %
84 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
85 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
86 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
87 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
88 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
89 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
92 self._Raise("Section size must be provided when using end-at-4gb")
93 if self._skip_at_start is not None:
94 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
96 self._skip_at_start = 0x100000000 - self._size
98 if self._skip_at_start is None:
99 self._skip_at_start = 0
100 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
102 def _ReadEntries(self):
103 for node in self._node.subnodes:
104 if node.name == 'hash':
106 entry = Entry.Create(self, node)
107 entry.SetPrefix(self._name_prefix)
108 self._entries[node.name] = entry
111 """Get the set of device tree files used by this image"""
113 for entry in self._entries.values():
114 fdt_set.update(entry.GetFdtSet())
117 def SetOffset(self, offset):
118 self._offset = offset
120 def ExpandEntries(self):
121 for entry in self._entries.values():
122 entry.ExpandEntries()
124 def AddMissingProperties(self):
125 """Add new properties to the device tree as needed for this entry"""
126 for prop in ['offset', 'size', 'image-pos']:
127 if not prop in self._node.props:
128 state.AddZeroProp(self._node, prop)
129 state.CheckAddHashProp(self._node)
130 for entry in self._entries.values():
131 entry.AddMissingProperties()
133 def SetCalculatedProperties(self):
134 state.SetInt(self._node, 'offset', self._offset or 0)
135 state.SetInt(self._node, 'size', self._size)
136 image_pos = self._image_pos
137 if self._parent_section:
138 image_pos -= self._parent_section.GetRootSkipAtStart()
139 state.SetInt(self._node, 'image-pos', image_pos)
140 for entry in self._entries.values():
141 entry.SetCalculatedProperties()
143 def ProcessFdt(self, fdt):
144 todo = self._entries.values()
145 for passnum in range(3):
148 if not entry.ProcessFdt(fdt):
149 next_todo.append(entry)
154 self._Raise('Internal error: Could not complete processing of Fdt: '
155 'remaining %s' % todo)
159 """Check that the section contents does not exceed its size, etc."""
161 for entry in self._entries.values():
162 contents_size = max(contents_size, entry.offset + entry.size)
164 contents_size -= self._skip_at_start
168 size = self._pad_before + contents_size + self._pad_after
169 size = tools.Align(size, self._align_size)
171 if self._size and contents_size > self._size:
172 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
173 (contents_size, contents_size, self._size, self._size))
176 if self._size != tools.Align(self._size, self._align_size):
177 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
178 (self._size, self._size, self._align_size, self._align_size))
181 def _Raise(self, msg):
182 """Raises an error for this section
185 msg: Error message to use in the raise string
189 raise ValueError("Section '%s': %s" % (self._node.path, msg))
192 """Get the path of an image (in the FDT)
195 Full path of the node for this image
197 return self._node.path
199 def FindEntryType(self, etype):
200 """Find an entry type in the section
203 etype: Entry type to find
205 entry matching that type, or None if not found
207 for entry in self._entries.values():
208 if entry.etype == etype:
212 def GetEntryContents(self):
213 """Call ObtainContents() for each entry
215 This calls each entry's ObtainContents() a few times until they all
216 return True. We stop calling an entry's function once it returns
217 True. This allows the contents of one entry to depend on another.
219 After 3 rounds we give up since it's likely an error.
221 todo = self._entries.values()
222 for passnum in range(3):
225 if not entry.ObtainContents():
226 next_todo.append(entry)
231 self._Raise('Internal error: Could not complete processing of '
232 'contents: remaining %s' % todo)
235 def _SetEntryOffsetSize(self, name, offset, size):
236 """Set the offset and size of an entry
239 name: Entry name to update
243 entry = self._entries.get(name)
245 self._Raise("Unable to set offset/size for unknown entry '%s'" %
247 entry.SetOffsetSize(self._skip_at_start + offset, size)
249 def GetEntryOffsets(self):
250 """Handle entries that want to set the offset/size of other entries
252 This calls each entry's GetOffsets() method. If it returns a list
253 of entries to update, it updates them.
255 for entry in self._entries.values():
256 offset_dict = entry.GetOffsets()
257 for name, info in offset_dict.iteritems():
258 self._SetEntryOffsetSize(name, *info)
260 def PackEntries(self):
261 """Pack all entries into the section"""
262 offset = self._skip_at_start
263 for entry in self._entries.values():
264 offset = entry.Pack(offset)
265 self._size = self.CheckSize()
267 def _SortEntries(self):
268 """Sort entries by offset"""
269 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
270 self._entries.clear()
271 for entry in entries:
272 self._entries[entry._node.name] = entry
274 def _ExpandEntries(self):
275 """Expand any entries that are permitted to"""
277 for entry in self._entries.values():
279 exp_entry.ExpandToLimit(entry.offset)
281 if entry.expand_size:
284 exp_entry.ExpandToLimit(self._size)
286 def CheckEntries(self):
287 """Check that entries do not overlap or extend outside the section
289 This also sorts entries, if needed and expands
293 self._ExpandEntries()
296 for entry in self._entries.values():
298 if (entry.offset < self._skip_at_start or
299 entry.offset + entry.size > self._skip_at_start + self._size):
300 entry.Raise("Offset %#x (%d) is outside the section starting "
302 (entry.offset, entry.offset, self._skip_at_start,
303 self._skip_at_start))
304 if entry.offset < offset:
305 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
306 "ending at %#x (%d)" %
307 (entry.offset, entry.offset, prev_name, offset, offset))
308 offset = entry.offset + entry.size
309 prev_name = entry.GetPath()
311 def SetImagePos(self, image_pos):
312 self._image_pos = image_pos
313 for entry in self._entries.values():
314 entry.SetImagePos(image_pos)
316 def ProcessEntryContents(self):
317 """Call the ProcessContents() method for each entry
319 This is intended to adjust the contents as needed by the entry type.
321 for entry in self._entries.values():
322 entry.ProcessContents()
324 def WriteSymbols(self):
325 """Write symbol values into binary files for access at run time"""
326 for entry in self._entries.values():
327 entry.WriteSymbols(self)
329 def BuildSection(self, fd, base_offset):
330 """Write the section to a file"""
332 fd.write(self.GetData())
335 """Get the contents of the section"""
336 section_data = chr(self._pad_byte) * self._size
338 for entry in self._entries.values():
339 data = entry.GetData()
340 base = self._pad_before + entry.offset - self._skip_at_start
341 section_data = (section_data[:base] + data +
342 section_data[base + len(data):])
345 def LookupSymbol(self, sym_name, optional, msg):
346 """Look up a symbol in an ELF file
348 Looks up a symbol in an ELF file. Only entry types which come from an
349 ELF image can be used by this function.
351 At present the only entry property supported is offset.
354 sym_name: Symbol name in the ELF file to look up in the format
355 _binman_<entry>_prop_<property> where <entry> is the name of
356 the entry and <property> is the property to find (e.g.
357 _binman_u_boot_prop_offset). As a special case, you can append
358 _any to <entry> to have it search for any matching entry. E.g.
359 _binman_u_boot_any_prop_offset will match entries called u-boot,
360 u-boot-img and u-boot-nodtb)
361 optional: True if the symbol is optional. If False this function
362 will raise if the symbol is not found
363 msg: Message to display if an error occurs
366 Value that should be assigned to that symbol, or None if it was
367 optional and not found
370 ValueError if the symbol is invalid or not found, or references a
371 property which is not supported
373 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
375 raise ValueError("%s: Symbol '%s' has invalid format" %
377 entry_name, prop_name = m.groups()
378 entry_name = entry_name.replace('_', '-')
379 entry = self._entries.get(entry_name)
381 if entry_name.endswith('-any'):
382 root = entry_name[:-4]
383 for name in self._entries:
384 if name.startswith(root):
385 rest = name[len(root):]
386 if rest in ['', '-img', '-nodtb']:
387 entry = self._entries[name]
389 err = ("%s: Entry '%s' not found in list (%s)" %
390 (msg, entry_name, ','.join(self._entries.keys())))
392 print('Warning: %s' % err, file=sys.stderr)
394 raise ValueError(err)
395 if prop_name == 'offset':
397 elif prop_name == 'image_pos':
398 return entry.image_pos
400 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
402 def GetEntries(self):
403 """Get the number of entries in a section
406 Number of entries in a section
411 """Get the size of a section in bytes
413 This is only meaningful if the section has a pre-defined size, or the
414 entries within it have been packed, so that the size has been
422 def WriteMap(self, fd, indent):
423 """Write a map of the section to a .map file
426 fd: File to write the map to
428 Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
429 self._size, self._image_pos)
430 for entry in self._entries.values():
431 entry.WriteMap(fd, indent + 1)
433 def GetContentsByPhandle(self, phandle, source_entry):
434 """Get the data contents of an entry specified by a phandle
436 This uses a phandle to look up a node and and find the entry
437 associated with it. Then it returnst he contents of that entry.
440 phandle: Phandle to look up (integer)
441 source_entry: Entry containing that phandle (used for error
445 data from associated entry (as a string), or None if not found
447 node = self._node.GetFdt().LookupPhandle(phandle)
449 source_entry.Raise("Cannot find node for phandle %d" % phandle)
450 for entry in self._entries.values():
451 if entry._node == node:
452 return entry.GetData()
453 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
455 def ExpandSize(self, size):
456 if size != self._size:
459 def GetRootSkipAtStart(self):
460 if self._parent_section:
461 return self._parent_section.GetRootSkipAtStart()
462 return self._skip_at_start
464 def GetImageSize(self):
465 return self._image._size