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 = None
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 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
85 self._Raise("Section size must be provided when using end-at-4gb")
86 if self._skip_at_start is not None:
87 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
89 self._skip_at_start = 0x100000000 - self._size
91 if self._skip_at_start is None:
92 self._skip_at_start = 0
93 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
95 def _ReadEntries(self):
96 for node in self._node.subnodes:
97 entry = Entry.Create(self, node)
98 entry.SetPrefix(self._name_prefix)
99 self._entries[node.name] = entry
101 def SetOffset(self, offset):
102 self._offset = offset
104 def AddMissingProperties(self):
105 """Add new properties to the device tree as needed for this entry"""
106 for prop in ['offset', 'size', 'image-pos']:
107 if not prop in self._node.props:
108 self._node.AddZeroProp(prop)
109 for entry in self._entries.values():
110 entry.AddMissingProperties()
112 def SetCalculatedProperties(self):
113 self._node.SetInt('offset', self._offset)
114 self._node.SetInt('size', self._size)
115 self._node.SetInt('image-pos', self._image_pos)
116 for entry in self._entries.values():
117 entry.SetCalculatedProperties()
119 def ProcessFdt(self, fdt):
120 todo = self._entries.values()
121 for passnum in range(3):
124 if not entry.ProcessFdt(fdt):
125 next_todo.append(entry)
130 self._Raise('Internal error: Could not complete processing of Fdt: '
131 'remaining %s' % todo)
135 """Check that the section contents does not exceed its size, etc."""
137 for entry in self._entries.values():
138 contents_size = max(contents_size, entry.offset + entry.size)
140 contents_size -= self._skip_at_start
144 size = self._pad_before + contents_size + self._pad_after
145 size = tools.Align(size, self._align_size)
147 if self._size and contents_size > self._size:
148 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
149 (contents_size, contents_size, self._size, self._size))
152 if self._size != tools.Align(self._size, self._align_size):
153 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
154 (self._size, self._size, self._align_size, self._align_size))
157 def _Raise(self, msg):
158 """Raises an error for this section
161 msg: Error message to use in the raise string
165 raise ValueError("Section '%s': %s" % (self._node.path, msg))
168 """Get the path of an image (in the FDT)
171 Full path of the node for this image
173 return self._node.path
175 def FindEntryType(self, etype):
176 """Find an entry type in the section
179 etype: Entry type to find
181 entry matching that type, or None if not found
183 for entry in self._entries.values():
184 if entry.etype == etype:
188 def GetEntryContents(self):
189 """Call ObtainContents() for each entry
191 This calls each entry's ObtainContents() a few times until they all
192 return True. We stop calling an entry's function once it returns
193 True. This allows the contents of one entry to depend on another.
195 After 3 rounds we give up since it's likely an error.
197 todo = self._entries.values()
198 for passnum in range(3):
201 if not entry.ObtainContents():
202 next_todo.append(entry)
207 self._Raise('Internal error: Could not complete processing of '
208 'contents: remaining %s' % todo)
211 def _SetEntryOffsetSize(self, name, offset, size):
212 """Set the offset and size of an entry
215 name: Entry name to update
219 entry = self._entries.get(name)
221 self._Raise("Unable to set offset/size for unknown entry '%s'" %
223 entry.SetOffsetSize(self._skip_at_start + offset, size)
225 def GetEntryOffsets(self):
226 """Handle entries that want to set the offset/size of other entries
228 This calls each entry's GetOffsets() method. If it returns a list
229 of entries to update, it updates them.
231 for entry in self._entries.values():
232 offset_dict = entry.GetOffsets()
233 for name, info in offset_dict.iteritems():
234 self._SetEntryOffsetSize(name, *info)
236 def PackEntries(self):
237 """Pack all entries into the section"""
238 offset = self._skip_at_start
239 for entry in self._entries.values():
240 offset = entry.Pack(offset)
241 self._size = self.CheckSize()
243 def _SortEntries(self):
244 """Sort entries by offset"""
245 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
246 self._entries.clear()
247 for entry in entries:
248 self._entries[entry._node.name] = entry
250 def CheckEntries(self):
251 """Check that entries do not overlap or extend outside the section"""
256 for entry in self._entries.values():
258 if (entry.offset < self._skip_at_start or
259 entry.offset >= self._skip_at_start + self._size):
260 entry.Raise("Offset %#x (%d) is outside the section starting "
262 (entry.offset, entry.offset, self._skip_at_start,
263 self._skip_at_start))
264 if entry.offset < offset:
265 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
266 "ending at %#x (%d)" %
267 (entry.offset, entry.offset, prev_name, offset, offset))
268 offset = entry.offset + entry.size
269 prev_name = entry.GetPath()
271 def SetImagePos(self, image_pos):
272 self._image_pos = image_pos
273 for entry in self._entries.values():
274 entry.SetImagePos(image_pos)
276 def ProcessEntryContents(self):
277 """Call the ProcessContents() method for each entry
279 This is intended to adjust the contents as needed by the entry type.
281 for entry in self._entries.values():
282 entry.ProcessContents()
284 def WriteSymbols(self):
285 """Write symbol values into binary files for access at run time"""
286 for entry in self._entries.values():
287 entry.WriteSymbols(self)
289 def BuildSection(self, fd, base_offset):
290 """Write the section to a file"""
292 fd.write(self.GetData())
295 """Get the contents of the section"""
296 section_data = chr(self._pad_byte) * self._size
298 for entry in self._entries.values():
299 data = entry.GetData()
300 base = self._pad_before + entry.offset - self._skip_at_start
301 section_data = (section_data[:base] + data +
302 section_data[base + len(data):])
305 def LookupSymbol(self, sym_name, optional, msg):
306 """Look up a symbol in an ELF file
308 Looks up a symbol in an ELF file. Only entry types which come from an
309 ELF image can be used by this function.
311 At present the only entry property supported is offset.
314 sym_name: Symbol name in the ELF file to look up in the format
315 _binman_<entry>_prop_<property> where <entry> is the name of
316 the entry and <property> is the property to find (e.g.
317 _binman_u_boot_prop_offset). As a special case, you can append
318 _any to <entry> to have it search for any matching entry. E.g.
319 _binman_u_boot_any_prop_offset will match entries called u-boot,
320 u-boot-img and u-boot-nodtb)
321 optional: True if the symbol is optional. If False this function
322 will raise if the symbol is not found
323 msg: Message to display if an error occurs
326 Value that should be assigned to that symbol, or None if it was
327 optional and not found
330 ValueError if the symbol is invalid or not found, or references a
331 property which is not supported
333 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
335 raise ValueError("%s: Symbol '%s' has invalid format" %
337 entry_name, prop_name = m.groups()
338 entry_name = entry_name.replace('_', '-')
339 entry = self._entries.get(entry_name)
341 if entry_name.endswith('-any'):
342 root = entry_name[:-4]
343 for name in self._entries:
344 if name.startswith(root):
345 rest = name[len(root):]
346 if rest in ['', '-img', '-nodtb']:
347 entry = self._entries[name]
349 err = ("%s: Entry '%s' not found in list (%s)" %
350 (msg, entry_name, ','.join(self._entries.keys())))
352 print('Warning: %s' % err, file=sys.stderr)
354 raise ValueError(err)
355 if prop_name == 'offset':
357 elif prop_name == 'image_pos':
358 return entry.image_pos
360 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
362 def GetEntries(self):
363 """Get the number of entries in a section
366 Number of entries in a section
371 """Get the size of a section in bytes
373 This is only meaningful if the section has a pre-defined size, or the
374 entries within it have been packed, so that the size has been
382 def WriteMap(self, fd, indent):
383 """Write a map of the section to a .map file
386 fd: File to write the map to
388 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
390 for entry in self._entries.values():
391 entry.WriteMap(fd, indent + 1)
393 def GetContentsByPhandle(self, phandle, source_entry):
394 """Get the data contents of an entry specified by a phandle
396 This uses a phandle to look up a node and and find the entry
397 associated with it. Then it returnst he contents of that entry.
400 phandle: Phandle to look up (integer)
401 source_entry: Entry containing that phandle (used for error
405 data from associated entry (as a string), or None if not found
407 node = self._node.GetFdt().LookupPhandle(phandle)
409 source_entry.Raise("Cannot find node for phandle %d" % phandle)
410 for entry in self._entries.values():
411 if entry._node == node:
412 if entry.data is None:
415 source_entry.Raise("Cannot find entry for node '%s'" % node.name)