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 = 0
69 self._name_prefix = ''
70 self._entries = OrderedDict()
71 self._image_pos = None
77 """Read properties from the section node"""
78 self._size = fdt_util.GetInt(self._node, 'size')
79 self._align_size = fdt_util.GetInt(self._node, 'align-size')
80 if tools.NotPowerOfTwo(self._align_size):
81 self._Raise("Alignment size %s must be a power of two" %
83 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
84 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
85 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
86 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
87 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
88 if self._end_4gb and not self._size:
89 self._Raise("Section size must be provided when using end-at-4gb")
91 self._skip_at_start = 0x100000000 - self._size
92 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
94 def _ReadEntries(self):
95 for node in self._node.subnodes:
96 if node.name == 'hash':
98 entry = Entry.Create(self, node)
99 entry.SetPrefix(self._name_prefix)
100 self._entries[node.name] = entry
103 """Get the set of device tree files used by this image"""
105 for entry in self._entries.values():
106 fdt_set.update(entry.GetFdtSet())
109 def SetOffset(self, offset):
110 self._offset = offset
112 def ExpandEntries(self):
113 for entry in self._entries.values():
114 entry.ExpandEntries()
116 def AddMissingProperties(self):
117 """Add new properties to the device tree as needed for this entry"""
118 for prop in ['offset', 'size', 'image-pos']:
119 if not prop in self._node.props:
120 state.AddZeroProp(self._node, prop)
121 state.CheckAddHashProp(self._node)
122 for entry in self._entries.values():
123 entry.AddMissingProperties()
125 def SetCalculatedProperties(self):
126 state.SetInt(self._node, 'offset', self._offset)
127 state.SetInt(self._node, 'size', self._size)
128 image_pos = self._image_pos
129 if self._parent_section:
130 image_pos -= self._parent_section.GetRootSkipAtStart()
131 state.SetInt(self._node, 'image-pos', image_pos)
132 for entry in self._entries.values():
133 entry.SetCalculatedProperties()
135 def ProcessFdt(self, fdt):
136 todo = self._entries.values()
137 for passnum in range(3):
140 if not entry.ProcessFdt(fdt):
141 next_todo.append(entry)
146 self._Raise('Internal error: Could not complete processing of Fdt: '
147 'remaining %s' % todo)
151 """Check that the section contents does not exceed its size, etc."""
153 for entry in self._entries.values():
154 contents_size = max(contents_size, entry.offset + entry.size)
156 contents_size -= self._skip_at_start
160 size = self._pad_before + contents_size + self._pad_after
161 size = tools.Align(size, self._align_size)
163 if self._size and contents_size > self._size:
164 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
165 (contents_size, contents_size, self._size, self._size))
168 if self._size != tools.Align(self._size, self._align_size):
169 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
170 (self._size, self._size, self._align_size, self._align_size))
173 def _Raise(self, msg):
174 """Raises an error for this section
177 msg: Error message to use in the raise string
181 raise ValueError("Section '%s': %s" % (self._node.path, msg))
184 """Get the path of an image (in the FDT)
187 Full path of the node for this image
189 return self._node.path
191 def FindEntryType(self, etype):
192 """Find an entry type in the section
195 etype: Entry type to find
197 entry matching that type, or None if not found
199 for entry in self._entries.values():
200 if entry.etype == etype:
204 def GetEntryContents(self):
205 """Call ObtainContents() for each entry
207 This calls each entry's ObtainContents() a few times until they all
208 return True. We stop calling an entry's function once it returns
209 True. This allows the contents of one entry to depend on another.
211 After 3 rounds we give up since it's likely an error.
213 todo = self._entries.values()
214 for passnum in range(3):
217 if not entry.ObtainContents():
218 next_todo.append(entry)
223 self._Raise('Internal error: Could not complete processing of '
224 'contents: remaining %s' % todo)
227 def _SetEntryOffsetSize(self, name, offset, size):
228 """Set the offset and size of an entry
231 name: Entry name to update
235 entry = self._entries.get(name)
237 self._Raise("Unable to set offset/size for unknown entry '%s'" %
239 entry.SetOffsetSize(self._skip_at_start + offset, size)
241 def GetEntryOffsets(self):
242 """Handle entries that want to set the offset/size of other entries
244 This calls each entry's GetOffsets() method. If it returns a list
245 of entries to update, it updates them.
247 for entry in self._entries.values():
248 offset_dict = entry.GetOffsets()
249 for name, info in offset_dict.iteritems():
250 self._SetEntryOffsetSize(name, *info)
252 def PackEntries(self):
253 """Pack all entries into the section"""
254 offset = self._skip_at_start
255 for entry in self._entries.values():
256 offset = entry.Pack(offset)
257 self._size = self.CheckSize()
259 def _SortEntries(self):
260 """Sort entries by offset"""
261 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
262 self._entries.clear()
263 for entry in entries:
264 self._entries[entry._node.name] = entry
266 def _ExpandEntries(self):
267 """Expand any entries that are permitted to"""
269 for entry in self._entries.values():
271 exp_entry.ExpandToLimit(entry.offset)
273 if entry.expand_size:
276 exp_entry.ExpandToLimit(self._size)
278 def CheckEntries(self):
279 """Check that entries do not overlap or extend outside the section
281 This also sorts entries, if needed and expands
285 self._ExpandEntries()
288 for entry in self._entries.values():
290 if (entry.offset < self._skip_at_start or
291 entry.offset + entry.size > self._skip_at_start + self._size):
292 entry.Raise("Offset %#x (%d) is outside the section starting "
294 (entry.offset, entry.offset, self._skip_at_start,
295 self._skip_at_start))
296 if entry.offset < offset:
297 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
298 "ending at %#x (%d)" %
299 (entry.offset, entry.offset, prev_name, offset, offset))
300 offset = entry.offset + entry.size
301 prev_name = entry.GetPath()
303 def SetImagePos(self, image_pos):
304 self._image_pos = image_pos
305 for entry in self._entries.values():
306 entry.SetImagePos(image_pos)
308 def ProcessEntryContents(self):
309 """Call the ProcessContents() method for each entry
311 This is intended to adjust the contents as needed by the entry type.
313 for entry in self._entries.values():
314 entry.ProcessContents()
316 def WriteSymbols(self):
317 """Write symbol values into binary files for access at run time"""
318 for entry in self._entries.values():
319 entry.WriteSymbols(self)
321 def BuildSection(self, fd, base_offset):
322 """Write the section to a file"""
324 fd.write(self.GetData())
327 """Get the contents of the section"""
328 section_data = chr(self._pad_byte) * self._size
330 for entry in self._entries.values():
331 data = entry.GetData()
332 base = self._pad_before + entry.offset - self._skip_at_start
333 section_data = (section_data[:base] + data +
334 section_data[base + len(data):])
337 def LookupSymbol(self, sym_name, optional, msg):
338 """Look up a symbol in an ELF file
340 Looks up a symbol in an ELF file. Only entry types which come from an
341 ELF image can be used by this function.
343 At present the only entry property supported is offset.
346 sym_name: Symbol name in the ELF file to look up in the format
347 _binman_<entry>_prop_<property> where <entry> is the name of
348 the entry and <property> is the property to find (e.g.
349 _binman_u_boot_prop_offset). As a special case, you can append
350 _any to <entry> to have it search for any matching entry. E.g.
351 _binman_u_boot_any_prop_offset will match entries called u-boot,
352 u-boot-img and u-boot-nodtb)
353 optional: True if the symbol is optional. If False this function
354 will raise if the symbol is not found
355 msg: Message to display if an error occurs
358 Value that should be assigned to that symbol, or None if it was
359 optional and not found
362 ValueError if the symbol is invalid or not found, or references a
363 property which is not supported
365 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
367 raise ValueError("%s: Symbol '%s' has invalid format" %
369 entry_name, prop_name = m.groups()
370 entry_name = entry_name.replace('_', '-')
371 entry = self._entries.get(entry_name)
373 if entry_name.endswith('-any'):
374 root = entry_name[:-4]
375 for name in self._entries:
376 if name.startswith(root):
377 rest = name[len(root):]
378 if rest in ['', '-img', '-nodtb']:
379 entry = self._entries[name]
381 err = ("%s: Entry '%s' not found in list (%s)" %
382 (msg, entry_name, ','.join(self._entries.keys())))
384 print('Warning: %s' % err, file=sys.stderr)
386 raise ValueError(err)
387 if prop_name == 'offset':
389 elif prop_name == 'image_pos':
390 return entry.image_pos
392 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
394 def GetEntries(self):
395 """Get the number of entries in a section
398 Number of entries in a section
403 """Get the size of a section in bytes
405 This is only meaningful if the section has a pre-defined size, or the
406 entries within it have been packed, so that the size has been
414 def WriteMap(self, fd, indent):
415 """Write a map of the section to a .map file
418 fd: File to write the map to
420 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
422 for entry in self._entries.values():
423 entry.WriteMap(fd, indent + 1)
425 def GetContentsByPhandle(self, phandle, source_entry):
426 """Get the data contents of an entry specified by a phandle
428 This uses a phandle to look up a node and and find the entry
429 associated with it. Then it returnst he contents of that entry.
432 phandle: Phandle to look up (integer)
433 source_entry: Entry containing that phandle (used for error
437 data from associated entry (as a string), or None if not found
439 node = self._node.GetFdt().LookupPhandle(phandle)
441 source_entry.Raise("Cannot find node for phandle %d" % phandle)
442 for entry in self._entries.values():
443 if entry._node == node:
444 return entry.GetData()
445 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
447 def ExpandSize(self, size):
448 if size != self._size:
451 def GetRootSkipAtStart(self):
452 if self._parent_section:
453 return self._parent_section.GetRootSkipAtStart()
454 return self._skip_at_start
456 def GetImageSize(self):
457 return self._image._size