1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 """Entry-type module for sections (groups of entries)
7 Sections are entries which can contain other entries. This allows hierarchical
11 from __future__ import print_function
13 from collections import OrderedDict
17 from entry import Entry
22 class Entry_section(Entry):
23 """Entry that contains other entries
25 Properties / Entry arguments: (see binman README for more information)
26 pad-byte: Pad byte to use when padding
27 sort-by-offset: True if entries should be sorted by offset, False if
28 they must be in-order in the device tree description
29 end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
30 skip-at-start: Number of bytes before the first entry starts. These
31 effectively adjust the starting offset of entries. For example,
32 if this is 16, then the first entry would start at 16. An entry
33 with offset = 20 would in fact be written at offset 4 in the image
34 file, since the first 16 bytes are skipped when writing.
35 name-prefix: Adds a prefix to the name of every entry in the section
36 when writing out the map
38 Since a section is also an entry, it inherits all the properies of entries
41 A section is an entry which can contain other entries, thus allowing
42 hierarchical images to be created. See 'Sections and hierarchical images'
43 in the binman README for more information.
45 def __init__(self, section, etype, node, test=False):
47 Entry.__init__(self, section, etype, node)
49 self.image = section.image
50 self._entries = OrderedDict()
53 self._skip_at_start = None
57 """Read properties from the image node"""
59 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
60 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
61 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
62 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
65 self.Raise("Section size must be provided when using end-at-4gb")
66 if self._skip_at_start is not None:
67 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
69 self._skip_at_start = 0x100000000 - self.size
71 if self._skip_at_start is None:
72 self._skip_at_start = 0
73 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
74 filename = fdt_util.GetString(self._node, 'filename')
76 self._filename = filename
80 def _ReadEntries(self):
81 for node in self._node.subnodes:
82 if node.name == 'hash':
84 entry = Entry.Create(self, node)
86 entry.SetPrefix(self._name_prefix)
87 self._entries[node.name] = entry
89 def _Raise(self, msg):
90 """Raises an error for this section
93 msg: Error message to use in the raise string
97 raise ValueError("Section '%s': %s" % (self._node.path, msg))
101 for entry in self._entries.values():
102 fdts.update(entry.GetFdts())
105 def ProcessFdt(self, fdt):
106 """Allow entries to adjust the device tree
108 Some entries need to adjust the device tree for their purposes. This
109 may involve adding or deleting properties.
111 todo = self._entries.values()
112 for passnum in range(3):
115 if not entry.ProcessFdt(fdt):
116 next_todo.append(entry)
121 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
125 def ExpandEntries(self):
126 """Expand out any entries which have calculated sub-entries
128 Some entries are expanded out at runtime, e.g. 'files', which produces
129 a section containing a list of files. Process these entries so that
130 this information is added to the device tree.
132 Entry.ExpandEntries(self)
133 for entry in self._entries.values():
134 entry.ExpandEntries()
136 def AddMissingProperties(self):
137 """Add new properties to the device tree as needed for this entry"""
138 Entry.AddMissingProperties(self)
139 for entry in self._entries.values():
140 entry.AddMissingProperties()
142 def ObtainContents(self):
143 return self.GetEntryContents()
146 section_data = tools.GetBytes(self._pad_byte, self.size)
148 for entry in self._entries.values():
149 data = entry.GetData()
150 base = self.pad_before + entry.offset - self._skip_at_start
151 section_data = (section_data[:base] + data +
152 section_data[base + len(data):])
153 self.Detail('GetData: %d entries, total size %#x' %
154 (len(self._entries), len(section_data)))
157 def GetOffsets(self):
158 """Handle entries that want to set the offset/size of other entries
160 This calls each entry's GetOffsets() method. If it returns a list
161 of entries to update, it updates them.
163 self.GetEntryOffsets()
166 def ResetForPack(self):
167 """Reset offset/size fields so that packing can be done again"""
168 Entry.ResetForPack(self)
169 for entry in self._entries.values():
172 def Pack(self, offset):
173 """Pack all entries into the section"""
175 return Entry.Pack(self, offset)
177 def _PackEntries(self):
178 """Pack all entries into the image"""
179 offset = self._skip_at_start
180 for entry in self._entries.values():
181 offset = entry.Pack(offset)
182 self.size = self.CheckSize()
184 def _ExpandEntries(self):
185 """Expand any entries that are permitted to"""
187 for entry in self._entries.values():
189 exp_entry.ExpandToLimit(entry.offset)
191 if entry.expand_size:
194 exp_entry.ExpandToLimit(self.size)
196 def _SortEntries(self):
197 """Sort entries by offset"""
198 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
199 self._entries.clear()
200 for entry in entries:
201 self._entries[entry._node.name] = entry
203 def CheckEntries(self):
204 """Check that entries do not overlap or extend outside the image"""
207 self._ExpandEntries()
210 for entry in self._entries.values():
212 if (entry.offset < self._skip_at_start or
213 entry.offset + entry.size > self._skip_at_start +
215 entry.Raise("Offset %#x (%d) is outside the section starting "
217 (entry.offset, entry.offset, self._skip_at_start,
218 self._skip_at_start))
219 if entry.offset < offset:
220 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
221 "ending at %#x (%d)" %
222 (entry.offset, entry.offset, prev_name, offset, offset))
223 offset = entry.offset + entry.size
224 prev_name = entry.GetPath()
226 def WriteSymbols(self, section):
227 """Write symbol values into binary files for access at run time"""
228 for entry in self._entries.values():
229 entry.WriteSymbols(self)
231 def SetCalculatedProperties(self):
232 Entry.SetCalculatedProperties(self)
233 for entry in self._entries.values():
234 entry.SetCalculatedProperties()
236 def SetImagePos(self, image_pos):
237 Entry.SetImagePos(self, image_pos)
238 for entry in self._entries.values():
239 entry.SetImagePos(image_pos + self.offset)
241 def ProcessContents(self):
242 sizes_ok_base = super(Entry_section, self).ProcessContents()
244 for entry in self._entries.values():
245 if not entry.ProcessContents():
247 return sizes_ok and sizes_ok_base
249 def CheckOffset(self):
252 def WriteMap(self, fd, indent):
253 """Write a map of the section to a .map file
256 fd: File to write the map to
258 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
259 self.size, self.image_pos)
260 for entry in self._entries.values():
261 entry.WriteMap(fd, indent + 1)
263 def GetEntries(self):
266 def GetContentsByPhandle(self, phandle, source_entry):
267 """Get the data contents of an entry specified by a phandle
269 This uses a phandle to look up a node and and find the entry
270 associated with it. Then it returnst he contents of that entry.
273 phandle: Phandle to look up (integer)
274 source_entry: Entry containing that phandle (used for error
278 data from associated entry (as a string), or None if not found
280 node = self._node.GetFdt().LookupPhandle(phandle)
282 source_entry.Raise("Cannot find node for phandle %d" % phandle)
283 for entry in self._entries.values():
284 if entry._node == node:
285 return entry.GetData()
286 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
288 def LookupSymbol(self, sym_name, optional, msg):
289 """Look up a symbol in an ELF file
291 Looks up a symbol in an ELF file. Only entry types which come from an
292 ELF image can be used by this function.
294 At present the only entry property supported is offset.
297 sym_name: Symbol name in the ELF file to look up in the format
298 _binman_<entry>_prop_<property> where <entry> is the name of
299 the entry and <property> is the property to find (e.g.
300 _binman_u_boot_prop_offset). As a special case, you can append
301 _any to <entry> to have it search for any matching entry. E.g.
302 _binman_u_boot_any_prop_offset will match entries called u-boot,
303 u-boot-img and u-boot-nodtb)
304 optional: True if the symbol is optional. If False this function
305 will raise if the symbol is not found
306 msg: Message to display if an error occurs
309 Value that should be assigned to that symbol, or None if it was
310 optional and not found
313 ValueError if the symbol is invalid or not found, or references a
314 property which is not supported
316 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
318 raise ValueError("%s: Symbol '%s' has invalid format" %
320 entry_name, prop_name = m.groups()
321 entry_name = entry_name.replace('_', '-')
322 entry = self._entries.get(entry_name)
324 if entry_name.endswith('-any'):
325 root = entry_name[:-4]
326 for name in self._entries:
327 if name.startswith(root):
328 rest = name[len(root):]
329 if rest in ['', '-img', '-nodtb']:
330 entry = self._entries[name]
332 err = ("%s: Entry '%s' not found in list (%s)" %
333 (msg, entry_name, ','.join(self._entries.keys())))
335 print('Warning: %s' % err, file=sys.stderr)
337 raise ValueError(err)
338 if prop_name == 'offset':
340 elif prop_name == 'image_pos':
341 return entry.image_pos
343 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
345 def GetRootSkipAtStart(self):
346 """Get the skip-at-start value for the top-level section
348 This is used to find out the starting offset for root section that
349 contains this section. If this is a top-level section then it returns
350 the skip-at-start offset for this section.
352 This is used to get the absolute position of section within the image.
355 Integer skip-at-start value for the root section containing this
359 return self.section.GetRootSkipAtStart()
360 return self._skip_at_start
362 def GetStartOffset(self):
363 """Get the start offset for this section
366 The first available offset in this section (typically 0)
368 return self._skip_at_start
370 def GetImageSize(self):
371 """Get the size of the image containing this section
374 Image size as an integer number of bytes, which may be None if the
375 image size is dynamic and its sections have not yet been packed
377 return self.image.size
379 def FindEntryType(self, etype):
380 """Find an entry type in the section
383 etype: Entry type to find
385 entry matching that type, or None if not found
387 for entry in self._entries.values():
388 if entry.etype == etype:
392 def GetEntryContents(self):
393 """Call ObtainContents() for the section
395 todo = self._entries.values()
396 for passnum in range(3):
399 if not entry.ObtainContents():
400 next_todo.append(entry)
405 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
409 def _SetEntryOffsetSize(self, name, offset, size):
410 """Set the offset and size of an entry
413 name: Entry name to update
414 offset: New offset, or None to leave alone
415 size: New size, or None to leave alone
417 entry = self._entries.get(name)
419 self._Raise("Unable to set offset/size for unknown entry '%s'" %
421 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
424 def GetEntryOffsets(self):
425 """Handle entries that want to set the offset/size of other entries
427 This calls each entry's GetOffsets() method. If it returns a list
428 of entries to update, it updates them.
430 for entry in self._entries.values():
431 offset_dict = entry.GetOffsets()
432 for name, info in offset_dict.items():
433 self._SetEntryOffsetSize(name, *info)
437 """Check that the image contents does not exceed its size, etc."""
439 for entry in self._entries.values():
440 contents_size = max(contents_size, entry.offset + entry.size)
442 contents_size -= self._skip_at_start
446 size = self.pad_before + contents_size + self.pad_after
447 size = tools.Align(size, self.align_size)
449 if self.size and contents_size > self.size:
450 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
451 (contents_size, contents_size, self.size, self.size))
454 if self.size != tools.Align(self.size, self.align_size):
455 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
456 (self.size, self.size, self.align_size,
460 def ListEntries(self, entries, indent):
461 """List the files in the section"""
462 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
463 self.image_pos, None, self.offset, self)
464 for entry in self._entries.values():
465 entry.ListEntries(entries, indent + 1)
467 def LoadData(self, decomp=True):
468 for entry in self._entries.values():
469 entry.LoadData(decomp)
470 self.Detail('Loaded data')