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
56 self._align_size = None
61 self._skip_at_start = 0
63 self._name_prefix = ''
64 self._entries = OrderedDict()
70 """Read properties from the section node"""
71 self._size = fdt_util.GetInt(self._node, 'size')
72 self._align_size = fdt_util.GetInt(self._node, 'align-size')
73 if tools.NotPowerOfTwo(self._align_size):
74 self._Raise("Alignment size %s must be a power of two" %
76 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
77 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
78 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
79 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
80 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
81 if self._end_4gb and not self._size:
82 self._Raise("Section size must be provided when using end-at-4gb")
84 self._skip_at_start = 0x100000000 - self._size
85 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
87 def _ReadEntries(self):
88 for node in self._node.subnodes:
89 entry = Entry.Create(self, node)
90 entry.SetPrefix(self._name_prefix)
91 self._entries[node.name] = entry
93 def AddMissingProperties(self):
94 for entry in self._entries.values():
95 entry.AddMissingProperties()
97 def SetCalculatedProperties(self):
98 for entry in self._entries.values():
99 entry.SetCalculatedProperties()
101 def ProcessFdt(self, fdt):
102 todo = self._entries.values()
103 for passnum in range(3):
106 if not entry.ProcessFdt(fdt):
107 next_todo.append(entry)
112 self._Raise('Internal error: Could not complete processing of Fdt: '
113 'remaining %s' % todo)
117 """Check that the section contents does not exceed its size, etc."""
119 for entry in self._entries.values():
120 contents_size = max(contents_size, entry.offset + entry.size)
122 contents_size -= self._skip_at_start
126 size = self._pad_before + contents_size + self._pad_after
127 size = tools.Align(size, self._align_size)
129 if self._size and contents_size > self._size:
130 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
131 (contents_size, contents_size, self._size, self._size))
134 if self._size != tools.Align(self._size, self._align_size):
135 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
136 (self._size, self._size, self._align_size, self._align_size))
139 def _Raise(self, msg):
140 """Raises an error for this section
143 msg: Error message to use in the raise string
147 raise ValueError("Section '%s': %s" % (self._node.path, msg))
150 """Get the path of an image (in the FDT)
153 Full path of the node for this image
155 return self._node.path
157 def FindEntryType(self, etype):
158 """Find an entry type in the section
161 etype: Entry type to find
163 entry matching that type, or None if not found
165 for entry in self._entries.values():
166 if entry.etype == etype:
170 def GetEntryContents(self):
171 """Call ObtainContents() for each entry
173 This calls each entry's ObtainContents() a few times until they all
174 return True. We stop calling an entry's function once it returns
175 True. This allows the contents of one entry to depend on another.
177 After 3 rounds we give up since it's likely an error.
179 todo = self._entries.values()
180 for passnum in range(3):
183 if not entry.ObtainContents():
184 next_todo.append(entry)
189 self._Raise('Internal error: Could not complete processing of '
190 'contents: remaining %s' % todo)
193 def _SetEntryOffsetSize(self, name, offset, size):
194 """Set the offset and size of an entry
197 name: Entry name to update
201 entry = self._entries.get(name)
203 self._Raise("Unable to set offset/size for unknown entry '%s'" %
205 entry.SetOffsetSize(self._skip_at_start + offset, size)
207 def GetEntryOffsets(self):
208 """Handle entries that want to set the offset/size of other entries
210 This calls each entry's GetOffsets() method. If it returns a list
211 of entries to update, it updates them.
213 for entry in self._entries.values():
214 offset_dict = entry.GetOffsets()
215 for name, info in offset_dict.iteritems():
216 self._SetEntryOffsetSize(name, *info)
218 def PackEntries(self):
219 """Pack all entries into the section"""
220 offset = self._skip_at_start
221 for entry in self._entries.values():
222 offset = entry.Pack(offset)
223 self._size = self.CheckSize()
225 def _SortEntries(self):
226 """Sort entries by offset"""
227 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
228 self._entries.clear()
229 for entry in entries:
230 self._entries[entry._node.name] = entry
232 def CheckEntries(self):
233 """Check that entries do not overlap or extend outside the section"""
238 for entry in self._entries.values():
240 if (entry.offset < self._skip_at_start or
241 entry.offset >= self._skip_at_start + self._size):
242 entry.Raise("Offset %#x (%d) is outside the section starting "
244 (entry.offset, entry.offset, self._skip_at_start,
245 self._skip_at_start))
246 if entry.offset < offset:
247 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
248 "ending at %#x (%d)" %
249 (entry.offset, entry.offset, prev_name, offset, offset))
250 offset = entry.offset + entry.size
251 prev_name = entry.GetPath()
253 def ProcessEntryContents(self):
254 """Call the ProcessContents() method for each entry
256 This is intended to adjust the contents as needed by the entry type.
258 for entry in self._entries.values():
259 entry.ProcessContents()
261 def WriteSymbols(self):
262 """Write symbol values into binary files for access at run time"""
263 for entry in self._entries.values():
264 entry.WriteSymbols(self)
266 def BuildSection(self, fd, base_offset):
267 """Write the section to a file"""
269 fd.write(self.GetData())
272 """Write the section to a file"""
273 section_data = chr(self._pad_byte) * self._size
275 for entry in self._entries.values():
276 data = entry.GetData()
277 base = self._pad_before + entry.offset - self._skip_at_start
278 section_data = (section_data[:base] + data +
279 section_data[base + len(data):])
282 def LookupSymbol(self, sym_name, optional, msg):
283 """Look up a symbol in an ELF file
285 Looks up a symbol in an ELF file. Only entry types which come from an
286 ELF image can be used by this function.
288 At present the only entry property supported is offset.
291 sym_name: Symbol name in the ELF file to look up in the format
292 _binman_<entry>_prop_<property> where <entry> is the name of
293 the entry and <property> is the property to find (e.g.
294 _binman_u_boot_prop_offset). As a special case, you can append
295 _any to <entry> to have it search for any matching entry. E.g.
296 _binman_u_boot_any_prop_offset will match entries called u-boot,
297 u-boot-img and u-boot-nodtb)
298 optional: True if the symbol is optional. If False this function
299 will raise if the symbol is not found
300 msg: Message to display if an error occurs
303 Value that should be assigned to that symbol, or None if it was
304 optional and not found
307 ValueError if the symbol is invalid or not found, or references a
308 property which is not supported
310 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
312 raise ValueError("%s: Symbol '%s' has invalid format" %
314 entry_name, prop_name = m.groups()
315 entry_name = entry_name.replace('_', '-')
316 entry = self._entries.get(entry_name)
318 if entry_name.endswith('-any'):
319 root = entry_name[:-4]
320 for name in self._entries:
321 if name.startswith(root):
322 rest = name[len(root):]
323 if rest in ['', '-img', '-nodtb']:
324 entry = self._entries[name]
326 err = ("%s: Entry '%s' not found in list (%s)" %
327 (msg, entry_name, ','.join(self._entries.keys())))
329 print('Warning: %s' % err, file=sys.stderr)
331 raise ValueError(err)
332 if prop_name == 'offset':
335 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
337 def GetEntries(self):
340 def WriteMap(self, fd, indent):
341 """Write a map of the section to a .map file
344 fd: File to write the map to
346 for entry in self._entries.values():
347 entry.WriteMap(fd, indent)