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']:
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 for entry in self._entries.values():
109 entry.SetCalculatedProperties()
111 def ProcessFdt(self, fdt):
112 todo = self._entries.values()
113 for passnum in range(3):
116 if not entry.ProcessFdt(fdt):
117 next_todo.append(entry)
122 self._Raise('Internal error: Could not complete processing of Fdt: '
123 'remaining %s' % todo)
127 """Check that the section contents does not exceed its size, etc."""
129 for entry in self._entries.values():
130 contents_size = max(contents_size, entry.offset + entry.size)
132 contents_size -= self._skip_at_start
136 size = self._pad_before + contents_size + self._pad_after
137 size = tools.Align(size, self._align_size)
139 if self._size and contents_size > self._size:
140 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
141 (contents_size, contents_size, self._size, self._size))
144 if self._size != tools.Align(self._size, self._align_size):
145 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
146 (self._size, self._size, self._align_size, self._align_size))
149 def _Raise(self, msg):
150 """Raises an error for this section
153 msg: Error message to use in the raise string
157 raise ValueError("Section '%s': %s" % (self._node.path, msg))
160 """Get the path of an image (in the FDT)
163 Full path of the node for this image
165 return self._node.path
167 def FindEntryType(self, etype):
168 """Find an entry type in the section
171 etype: Entry type to find
173 entry matching that type, or None if not found
175 for entry in self._entries.values():
176 if entry.etype == etype:
180 def GetEntryContents(self):
181 """Call ObtainContents() for each entry
183 This calls each entry's ObtainContents() a few times until they all
184 return True. We stop calling an entry's function once it returns
185 True. This allows the contents of one entry to depend on another.
187 After 3 rounds we give up since it's likely an error.
189 todo = self._entries.values()
190 for passnum in range(3):
193 if not entry.ObtainContents():
194 next_todo.append(entry)
199 self._Raise('Internal error: Could not complete processing of '
200 'contents: remaining %s' % todo)
203 def _SetEntryOffsetSize(self, name, offset, size):
204 """Set the offset and size of an entry
207 name: Entry name to update
211 entry = self._entries.get(name)
213 self._Raise("Unable to set offset/size for unknown entry '%s'" %
215 entry.SetOffsetSize(self._skip_at_start + offset, size)
217 def GetEntryOffsets(self):
218 """Handle entries that want to set the offset/size of other entries
220 This calls each entry's GetOffsets() method. If it returns a list
221 of entries to update, it updates them.
223 for entry in self._entries.values():
224 offset_dict = entry.GetOffsets()
225 for name, info in offset_dict.iteritems():
226 self._SetEntryOffsetSize(name, *info)
228 def PackEntries(self):
229 """Pack all entries into the section"""
230 offset = self._skip_at_start
231 for entry in self._entries.values():
232 offset = entry.Pack(offset)
233 self._size = self.CheckSize()
235 def _SortEntries(self):
236 """Sort entries by offset"""
237 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
238 self._entries.clear()
239 for entry in entries:
240 self._entries[entry._node.name] = entry
242 def CheckEntries(self):
243 """Check that entries do not overlap or extend outside the section"""
248 for entry in self._entries.values():
250 if (entry.offset < self._skip_at_start or
251 entry.offset >= self._skip_at_start + self._size):
252 entry.Raise("Offset %#x (%d) is outside the section starting "
254 (entry.offset, entry.offset, self._skip_at_start,
255 self._skip_at_start))
256 if entry.offset < offset:
257 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
258 "ending at %#x (%d)" %
259 (entry.offset, entry.offset, prev_name, offset, offset))
260 offset = entry.offset + entry.size
261 prev_name = entry.GetPath()
263 def ProcessEntryContents(self):
264 """Call the ProcessContents() method for each entry
266 This is intended to adjust the contents as needed by the entry type.
268 for entry in self._entries.values():
269 entry.ProcessContents()
271 def WriteSymbols(self):
272 """Write symbol values into binary files for access at run time"""
273 for entry in self._entries.values():
274 entry.WriteSymbols(self)
276 def BuildSection(self, fd, base_offset):
277 """Write the section to a file"""
279 fd.write(self.GetData())
282 """Get the contents of the section"""
283 section_data = chr(self._pad_byte) * self._size
285 for entry in self._entries.values():
286 data = entry.GetData()
287 base = self._pad_before + entry.offset - self._skip_at_start
288 section_data = (section_data[:base] + data +
289 section_data[base + len(data):])
292 def LookupSymbol(self, sym_name, optional, msg):
293 """Look up a symbol in an ELF file
295 Looks up a symbol in an ELF file. Only entry types which come from an
296 ELF image can be used by this function.
298 At present the only entry property supported is offset.
301 sym_name: Symbol name in the ELF file to look up in the format
302 _binman_<entry>_prop_<property> where <entry> is the name of
303 the entry and <property> is the property to find (e.g.
304 _binman_u_boot_prop_offset). As a special case, you can append
305 _any to <entry> to have it search for any matching entry. E.g.
306 _binman_u_boot_any_prop_offset will match entries called u-boot,
307 u-boot-img and u-boot-nodtb)
308 optional: True if the symbol is optional. If False this function
309 will raise if the symbol is not found
310 msg: Message to display if an error occurs
313 Value that should be assigned to that symbol, or None if it was
314 optional and not found
317 ValueError if the symbol is invalid or not found, or references a
318 property which is not supported
320 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
322 raise ValueError("%s: Symbol '%s' has invalid format" %
324 entry_name, prop_name = m.groups()
325 entry_name = entry_name.replace('_', '-')
326 entry = self._entries.get(entry_name)
328 if entry_name.endswith('-any'):
329 root = entry_name[:-4]
330 for name in self._entries:
331 if name.startswith(root):
332 rest = name[len(root):]
333 if rest in ['', '-img', '-nodtb']:
334 entry = self._entries[name]
336 err = ("%s: Entry '%s' not found in list (%s)" %
337 (msg, entry_name, ','.join(self._entries.keys())))
339 print('Warning: %s' % err, file=sys.stderr)
341 raise ValueError(err)
342 if prop_name == 'offset':
345 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
347 def GetEntries(self):
348 """Get the number of entries in a section
351 Number of entries in a section
356 """Get the size of a section in bytes
358 This is only meaningful if the section has a pre-defined size, or the
359 entries within it have been packed, so that the size has been
367 def WriteMap(self, fd, indent):
368 """Write a map of the section to a .map file
371 fd: File to write the map to
373 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size)
374 for entry in self._entries.values():
375 entry.WriteMap(fd, indent + 1)