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
17 class Section(object):
18 """A section which contains multiple entries
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.
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
28 effectively changes the place where entry offset 0 starts
29 _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
32 _sort: True if entries should be sorted by offset, False if they
33 must be in-order in the device tree description
34 _skip_at_start: Number of bytes before the first entry starts. These
35 effectively adjust the starting offset of entries. For example,
36 if _pad_before is 16, then the first entry would start at 16.
37 An entry with offset = 20 would in fact be written at offset 4
39 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
40 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.
43 _name_prefix: Prefix to add to the name of all entries within this
45 _entries: OrderedDict() of entries
47 def __init__(self, name, node, test=False):
51 from entry import Entry
57 self._align_size = None
62 self._skip_at_start = 0
64 self._name_prefix = ''
65 self._entries = OrderedDict()
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" %
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)
80 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
81 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
82 if self._end_4gb and not self._size:
83 self._Raise("Section size must be provided when using end-at-4gb")
85 self._skip_at_start = 0x100000000 - self._size
86 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
88 def _ReadEntries(self):
89 for node in self._node.subnodes:
90 entry = Entry.Create(self, node)
91 entry.SetPrefix(self._name_prefix)
92 self._entries[node.name] = entry
94 def SetOffset(self, offset):
97 def AddMissingProperties(self):
98 """Add new properties to the device tree as needed for this entry"""
99 for prop in ['offset', 'size', 'image-pos']:
100 if not prop in self._node.props:
101 self._node.AddZeroProp(prop)
102 for entry in self._entries.values():
103 entry.AddMissingProperties()
105 def SetCalculatedProperties(self):
106 self._node.SetInt('offset', self._offset)
107 self._node.SetInt('size', self._size)
108 self._node.SetInt('image-pos', self._image_pos)
109 for entry in self._entries.values():
110 entry.SetCalculatedProperties()
112 def ProcessFdt(self, fdt):
113 todo = self._entries.values()
114 for passnum in range(3):
117 if not entry.ProcessFdt(fdt):
118 next_todo.append(entry)
123 self._Raise('Internal error: Could not complete processing of Fdt: '
124 'remaining %s' % todo)
128 """Check that the section contents does not exceed its size, etc."""
130 for entry in self._entries.values():
131 contents_size = max(contents_size, entry.offset + entry.size)
133 contents_size -= self._skip_at_start
137 size = self._pad_before + contents_size + self._pad_after
138 size = tools.Align(size, self._align_size)
140 if self._size and contents_size > self._size:
141 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
142 (contents_size, contents_size, self._size, self._size))
145 if self._size != tools.Align(self._size, self._align_size):
146 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
147 (self._size, self._size, self._align_size, self._align_size))
150 def _Raise(self, msg):
151 """Raises an error for this section
154 msg: Error message to use in the raise string
158 raise ValueError("Section '%s': %s" % (self._node.path, msg))
161 """Get the path of an image (in the FDT)
164 Full path of the node for this image
166 return self._node.path
168 def FindEntryType(self, etype):
169 """Find an entry type in the section
172 etype: Entry type to find
174 entry matching that type, or None if not found
176 for entry in self._entries.values():
177 if entry.etype == etype:
181 def GetEntryContents(self):
182 """Call ObtainContents() for each entry
184 This calls each entry's ObtainContents() a few times until they all
185 return True. We stop calling an entry's function once it returns
186 True. This allows the contents of one entry to depend on another.
188 After 3 rounds we give up since it's likely an error.
190 todo = self._entries.values()
191 for passnum in range(3):
194 if not entry.ObtainContents():
195 next_todo.append(entry)
200 self._Raise('Internal error: Could not complete processing of '
201 'contents: remaining %s' % todo)
204 def _SetEntryOffsetSize(self, name, offset, size):
205 """Set the offset and size of an entry
208 name: Entry name to update
212 entry = self._entries.get(name)
214 self._Raise("Unable to set offset/size for unknown entry '%s'" %
216 entry.SetOffsetSize(self._skip_at_start + offset, size)
218 def GetEntryOffsets(self):
219 """Handle entries that want to set the offset/size of other entries
221 This calls each entry's GetOffsets() method. If it returns a list
222 of entries to update, it updates them.
224 for entry in self._entries.values():
225 offset_dict = entry.GetOffsets()
226 for name, info in offset_dict.iteritems():
227 self._SetEntryOffsetSize(name, *info)
229 def PackEntries(self):
230 """Pack all entries into the section"""
231 offset = self._skip_at_start
232 for entry in self._entries.values():
233 offset = entry.Pack(offset)
234 self._size = self.CheckSize()
236 def _SortEntries(self):
237 """Sort entries by offset"""
238 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
239 self._entries.clear()
240 for entry in entries:
241 self._entries[entry._node.name] = entry
243 def CheckEntries(self):
244 """Check that entries do not overlap or extend outside the section"""
249 for entry in self._entries.values():
251 if (entry.offset < self._skip_at_start or
252 entry.offset >= self._skip_at_start + self._size):
253 entry.Raise("Offset %#x (%d) is outside the section starting "
255 (entry.offset, entry.offset, self._skip_at_start,
256 self._skip_at_start))
257 if entry.offset < offset:
258 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
259 "ending at %#x (%d)" %
260 (entry.offset, entry.offset, prev_name, offset, offset))
261 offset = entry.offset + entry.size
262 prev_name = entry.GetPath()
264 def SetImagePos(self, image_pos):
265 self._image_pos = image_pos
266 for entry in self._entries.values():
267 entry.SetImagePos(image_pos)
269 def ProcessEntryContents(self):
270 """Call the ProcessContents() method for each entry
272 This is intended to adjust the contents as needed by the entry type.
274 for entry in self._entries.values():
275 entry.ProcessContents()
277 def WriteSymbols(self):
278 """Write symbol values into binary files for access at run time"""
279 for entry in self._entries.values():
280 entry.WriteSymbols(self)
282 def BuildSection(self, fd, base_offset):
283 """Write the section to a file"""
285 fd.write(self.GetData())
288 """Get the contents of the section"""
289 section_data = chr(self._pad_byte) * self._size
291 for entry in self._entries.values():
292 data = entry.GetData()
293 base = self._pad_before + entry.offset - self._skip_at_start
294 section_data = (section_data[:base] + data +
295 section_data[base + len(data):])
298 def LookupSymbol(self, sym_name, optional, msg):
299 """Look up a symbol in an ELF file
301 Looks up a symbol in an ELF file. Only entry types which come from an
302 ELF image can be used by this function.
304 At present the only entry property supported is offset.
307 sym_name: Symbol name in the ELF file to look up in the format
308 _binman_<entry>_prop_<property> where <entry> is the name of
309 the entry and <property> is the property to find (e.g.
310 _binman_u_boot_prop_offset). As a special case, you can append
311 _any to <entry> to have it search for any matching entry. E.g.
312 _binman_u_boot_any_prop_offset will match entries called u-boot,
313 u-boot-img and u-boot-nodtb)
314 optional: True if the symbol is optional. If False this function
315 will raise if the symbol is not found
316 msg: Message to display if an error occurs
319 Value that should be assigned to that symbol, or None if it was
320 optional and not found
323 ValueError if the symbol is invalid or not found, or references a
324 property which is not supported
326 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
328 raise ValueError("%s: Symbol '%s' has invalid format" %
330 entry_name, prop_name = m.groups()
331 entry_name = entry_name.replace('_', '-')
332 entry = self._entries.get(entry_name)
334 if entry_name.endswith('-any'):
335 root = entry_name[:-4]
336 for name in self._entries:
337 if name.startswith(root):
338 rest = name[len(root):]
339 if rest in ['', '-img', '-nodtb']:
340 entry = self._entries[name]
342 err = ("%s: Entry '%s' not found in list (%s)" %
343 (msg, entry_name, ','.join(self._entries.keys())))
345 print('Warning: %s' % err, file=sys.stderr)
347 raise ValueError(err)
348 if prop_name == 'offset':
350 elif prop_name == 'image_pos':
351 return entry.image_pos
353 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
355 def GetEntries(self):
356 """Get the number of entries in a section
359 Number of entries in a section
364 """Get the size of a section in bytes
366 This is only meaningful if the section has a pre-defined size, or the
367 entries within it have been packed, so that the size has been
375 def WriteMap(self, fd, indent):
376 """Write a map of the section to a .map file
379 fd: File to write the map to
381 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
383 for entry in self._entries.values():
384 entry.WriteMap(fd, indent + 1)
386 def GetContentsByPhandle(self, phandle, source_entry):
387 """Get the data contents of an entry specified by a phandle
389 This uses a phandle to look up a node and and find the entry
390 associated with it. Then it returnst he contents of that entry.
393 phandle: Phandle to look up (integer)
394 source_entry: Entry containing that phandle (used for error
398 data from associated entry (as a string), or None if not found
400 node = self._node.GetFdt().LookupPhandle(phandle)
402 source_entry.Raise("Cannot find node for phandle %d" % phandle)
403 for entry in self._entries.values():
404 if entry._node == node:
405 if entry.data is None:
408 source_entry.Raise("Cannot find entry for node '%s'" % node.name)