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
13 from entry import Entry
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
49 _orig_offset: Original offset value read from node
50 _orig_size: Original size value read from node
52 def __init__(self, name, parent_section, node, image, test=False):
56 from entry import Entry
58 self._parent_section = parent_section
64 self._align_size = None
69 self._skip_at_start = None
71 self._name_prefix = ''
72 self._entries = OrderedDict()
73 self._image_pos = None
79 """Read properties from the section node"""
80 self._offset = fdt_util.GetInt(self._node, 'offset')
81 self._size = fdt_util.GetInt(self._node, 'size')
82 self._orig_offset = self._offset
83 self._orig_size = self._size
84 self._align_size = fdt_util.GetInt(self._node, 'align-size')
85 if tools.NotPowerOfTwo(self._align_size):
86 self._Raise("Alignment size %s must be a power of two" %
88 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
89 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
90 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
91 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
92 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
93 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
96 self._Raise("Section size must be provided when using end-at-4gb")
97 if self._skip_at_start is not None:
98 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
100 self._skip_at_start = 0x100000000 - self._size
102 if self._skip_at_start is None:
103 self._skip_at_start = 0
104 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
106 def _ReadEntries(self):
107 for node in self._node.subnodes:
108 if node.name == 'hash':
110 entry = Entry.Create(self, node)
111 entry.SetPrefix(self._name_prefix)
112 self._entries[node.name] = entry
115 """Get the set of device tree files used by this image"""
117 for entry in self._entries.values():
118 fdt_set.update(entry.GetFdtSet())
121 def SetOffset(self, offset):
122 self._offset = offset
124 def ExpandEntries(self):
125 for entry in self._entries.values():
126 entry.ExpandEntries()
128 def AddMissingProperties(self):
129 """Add new properties to the device tree as needed for this entry"""
130 for prop in ['offset', 'size', 'image-pos']:
131 if not prop in self._node.props:
132 state.AddZeroProp(self._node, prop)
133 state.CheckAddHashProp(self._node)
134 for entry in self._entries.values():
135 entry.AddMissingProperties()
137 def SetCalculatedProperties(self):
138 state.SetInt(self._node, 'offset', self._offset or 0)
139 state.SetInt(self._node, 'size', self._size)
140 image_pos = self._image_pos
141 if self._parent_section:
142 image_pos -= self._parent_section.GetRootSkipAtStart()
143 state.SetInt(self._node, 'image-pos', image_pos)
144 for entry in self._entries.values():
145 entry.SetCalculatedProperties()
147 def ProcessFdt(self, fdt):
148 todo = self._entries.values()
149 for passnum in range(3):
152 if not entry.ProcessFdt(fdt):
153 next_todo.append(entry)
158 self._Raise('Internal error: Could not complete processing of Fdt: '
159 'remaining %s' % todo)
163 """Check that the section contents does not exceed its size, etc."""
165 for entry in self._entries.values():
166 contents_size = max(contents_size, entry.offset + entry.size)
168 contents_size -= self._skip_at_start
172 size = self._pad_before + contents_size + self._pad_after
173 size = tools.Align(size, self._align_size)
175 if self._size and contents_size > self._size:
176 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
177 (contents_size, contents_size, self._size, self._size))
180 if self._size != tools.Align(self._size, self._align_size):
181 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
182 (self._size, self._size, self._align_size, self._align_size))
185 def _Raise(self, msg):
186 """Raises an error for this section
189 msg: Error message to use in the raise string
193 raise ValueError("Section '%s': %s" % (self._node.path, msg))
196 """Get the path of an image (in the FDT)
199 Full path of the node for this image
201 return self._node.path
203 def FindEntryType(self, etype):
204 """Find an entry type in the section
207 etype: Entry type to find
209 entry matching that type, or None if not found
211 for entry in self._entries.values():
212 if entry.etype == etype:
216 def GetEntryContents(self):
217 """Call ObtainContents() for each entry
219 This calls each entry's ObtainContents() a few times until they all
220 return True. We stop calling an entry's function once it returns
221 True. This allows the contents of one entry to depend on another.
223 After 3 rounds we give up since it's likely an error.
225 todo = self._entries.values()
226 for passnum in range(3):
229 if not entry.ObtainContents():
230 next_todo.append(entry)
235 self._Raise('Internal error: Could not complete processing of '
236 'contents: remaining %s' % todo)
239 def _SetEntryOffsetSize(self, name, offset, size):
240 """Set the offset and size of an entry
243 name: Entry name to update
244 offset: New offset, or None to leave alone
245 size: New size, or None to leave alone
247 entry = self._entries.get(name)
249 self._Raise("Unable to set offset/size for unknown entry '%s'" %
251 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
254 def GetEntryOffsets(self):
255 """Handle entries that want to set the offset/size of other entries
257 This calls each entry's GetOffsets() method. If it returns a list
258 of entries to update, it updates them.
260 for entry in self._entries.values():
261 offset_dict = entry.GetOffsets()
262 for name, info in offset_dict.items():
263 self._SetEntryOffsetSize(name, *info)
265 def ResetForPack(self):
266 """Reset offset/size fields so that packing can be done again"""
267 self._offset = self._orig_offset
268 self._size = self._orig_size
269 for entry in self._entries.values():
272 def PackEntries(self):
273 """Pack all entries into the section"""
274 offset = self._skip_at_start
275 for entry in self._entries.values():
276 offset = entry.Pack(offset)
277 self._size = self.CheckSize()
279 def _SortEntries(self):
280 """Sort entries by offset"""
281 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
282 self._entries.clear()
283 for entry in entries:
284 self._entries[entry._node.name] = entry
286 def _ExpandEntries(self):
287 """Expand any entries that are permitted to"""
289 for entry in self._entries.values():
291 exp_entry.ExpandToLimit(entry.offset)
293 if entry.expand_size:
296 exp_entry.ExpandToLimit(self._size)
298 def CheckEntries(self):
299 """Check that entries do not overlap or extend outside the section
301 This also sorts entries, if needed and expands
305 self._ExpandEntries()
308 for entry in self._entries.values():
310 if (entry.offset < self._skip_at_start or
311 entry.offset + entry.size > self._skip_at_start + self._size):
312 entry.Raise("Offset %#x (%d) is outside the section starting "
314 (entry.offset, entry.offset, self._skip_at_start,
315 self._skip_at_start))
316 if entry.offset < offset:
317 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
318 "ending at %#x (%d)" %
319 (entry.offset, entry.offset, prev_name, offset, offset))
320 offset = entry.offset + entry.size
321 prev_name = entry.GetPath()
323 def SetImagePos(self, image_pos):
324 self._image_pos = image_pos
325 for entry in self._entries.values():
326 entry.SetImagePos(image_pos)
328 def ProcessEntryContents(self):
329 """Call the ProcessContents() method for each entry
331 This is intended to adjust the contents as needed by the entry type.
334 True if no entries needed to change their size
337 for entry in self._entries.values():
338 if not entry.ProcessContents():
340 print("Entry '%s' size change" % self._node.path)
343 def WriteSymbols(self):
344 """Write symbol values into binary files for access at run time"""
345 for entry in self._entries.values():
346 entry.WriteSymbols(self)
348 def BuildSection(self, fd, base_offset):
349 """Write the section to a file"""
351 fd.write(self.GetData())
354 """Get the contents of the section"""
355 section_data = tools.GetBytes(self._pad_byte, self._size)
357 for entry in self._entries.values():
358 data = entry.GetData()
359 base = self._pad_before + entry.offset - self._skip_at_start
360 section_data = (section_data[:base] + data +
361 section_data[base + len(data):])
364 def LookupSymbol(self, sym_name, optional, msg):
365 """Look up a symbol in an ELF file
367 Looks up a symbol in an ELF file. Only entry types which come from an
368 ELF image can be used by this function.
370 At present the only entry property supported is offset.
373 sym_name: Symbol name in the ELF file to look up in the format
374 _binman_<entry>_prop_<property> where <entry> is the name of
375 the entry and <property> is the property to find (e.g.
376 _binman_u_boot_prop_offset). As a special case, you can append
377 _any to <entry> to have it search for any matching entry. E.g.
378 _binman_u_boot_any_prop_offset will match entries called u-boot,
379 u-boot-img and u-boot-nodtb)
380 optional: True if the symbol is optional. If False this function
381 will raise if the symbol is not found
382 msg: Message to display if an error occurs
385 Value that should be assigned to that symbol, or None if it was
386 optional and not found
389 ValueError if the symbol is invalid or not found, or references a
390 property which is not supported
392 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
394 raise ValueError("%s: Symbol '%s' has invalid format" %
396 entry_name, prop_name = m.groups()
397 entry_name = entry_name.replace('_', '-')
398 entry = self._entries.get(entry_name)
400 if entry_name.endswith('-any'):
401 root = entry_name[:-4]
402 for name in self._entries:
403 if name.startswith(root):
404 rest = name[len(root):]
405 if rest in ['', '-img', '-nodtb']:
406 entry = self._entries[name]
408 err = ("%s: Entry '%s' not found in list (%s)" %
409 (msg, entry_name, ','.join(self._entries.keys())))
411 print('Warning: %s' % err, file=sys.stderr)
413 raise ValueError(err)
414 if prop_name == 'offset':
416 elif prop_name == 'image_pos':
417 return entry.image_pos
419 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
421 def GetEntries(self):
422 """Get the dict of entries in a section
425 OrderedDict of entries in a section
430 """Get the size of a section in bytes
432 This is only meaningful if the section has a pre-defined size, or the
433 entries within it have been packed, so that the size has been
441 def WriteMap(self, fd, indent):
442 """Write a map of the section to a .map file
445 fd: File to write the map to
447 Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
448 self._size, self._image_pos)
449 for entry in self._entries.values():
450 entry.WriteMap(fd, indent + 1)
452 def GetContentsByPhandle(self, phandle, source_entry):
453 """Get the data contents of an entry specified by a phandle
455 This uses a phandle to look up a node and and find the entry
456 associated with it. Then it returnst he contents of that entry.
459 phandle: Phandle to look up (integer)
460 source_entry: Entry containing that phandle (used for error
464 data from associated entry (as a string), or None if not found
466 node = self._node.GetFdt().LookupPhandle(phandle)
468 source_entry.Raise("Cannot find node for phandle %d" % phandle)
469 for entry in self._entries.values():
470 if entry._node == node:
471 return entry.GetData()
472 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
474 def ExpandSize(self, size):
475 """Change the size of an entry
478 size: New size for entry
480 if size != self._size:
483 def GetRootSkipAtStart(self):
484 """Get the skip-at-start value for the top-level section
486 This is used to find out the starting offset for root section that
487 contains this section. If this is a top-level section then it returns
488 the skip-at-start offset for this section.
490 This is used to get the absolute position of section within the image.
493 Integer skip-at-start value for the root section containing this
496 if self._parent_section:
497 return self._parent_section.GetRootSkipAtStart()
498 return self._skip_at_start
500 def GetStartOffset(self):
501 """Get the start offset for this section
504 The first available offset in this section (typically 0)
506 return self._skip_at_start
508 def GetImageSize(self):
509 """Get the size of the image containing this section
512 Image size as an integer number of bytes, which may be None if the
513 image size is dynamic and its sections have not yet been packed
515 return self._image._size
517 def ListEntries(self, entries, indent):
518 """Override this method to list all files in the section"""
519 Entry.AddEntryInfo(entries, indent, self._name, 'section', self._size,
520 self._image_pos, None, self._offset,
521 self._parent_section)
522 for entry in self._entries.values():
523 entry.ListEntries(entries, indent + 1)