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 collections import OrderedDict
15 from binman.entry import Entry
16 from dtoc import fdt_util
17 from patman import tools
18 from patman import tout
21 class Entry_section(Entry):
22 """Entry that contains other entries
24 Properties / Entry arguments: (see binman README for more information)
25 pad-byte: Pad byte to use when padding
26 sort-by-offset: True if entries should be sorted by offset, False if
27 they must be in-order in the device tree description
28 end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
29 skip-at-start: Number of bytes before the first entry starts. These
30 effectively adjust the starting offset of entries. For example,
31 if this is 16, then the first entry would start at 16. An entry
32 with offset = 20 would in fact be written at offset 4 in the image
33 file, since the first 16 bytes are skipped when writing.
34 name-prefix: Adds a prefix to the name of every entry in the section
35 when writing out the map
37 Since a section is also an entry, it inherits all the properies of entries
40 A section is an entry which can contain other entries, thus allowing
41 hierarchical images to be created. See 'Sections and hierarchical images'
42 in the binman README for more information.
44 def __init__(self, section, etype, node, test=False):
46 super().__init__(section, etype, node)
47 self._entries = OrderedDict()
50 self._skip_at_start = None
54 """Read properties from the image node"""
56 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
57 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
58 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
59 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
62 self.Raise("Section size must be provided when using end-at-4gb")
63 if self._skip_at_start is not None:
64 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
66 self._skip_at_start = 0x100000000 - self.size
68 if self._skip_at_start is None:
69 self._skip_at_start = 0
70 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
71 filename = fdt_util.GetString(self._node, 'filename')
73 self._filename = filename
77 def _ReadEntries(self):
78 for node in self._node.subnodes:
79 if node.name == 'hash':
81 entry = Entry.Create(self, node)
83 entry.SetPrefix(self._name_prefix)
84 self._entries[node.name] = entry
86 def _Raise(self, msg):
87 """Raises an error for this section
90 msg: Error message to use in the raise string
94 raise ValueError("Section '%s': %s" % (self._node.path, msg))
98 for entry in self._entries.values():
99 fdts.update(entry.GetFdts())
102 def ProcessFdt(self, fdt):
103 """Allow entries to adjust the device tree
105 Some entries need to adjust the device tree for their purposes. This
106 may involve adding or deleting properties.
108 todo = self._entries.values()
109 for passnum in range(3):
112 if not entry.ProcessFdt(fdt):
113 next_todo.append(entry)
118 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
122 def ExpandEntries(self):
123 """Expand out any entries which have calculated sub-entries
125 Some entries are expanded out at runtime, e.g. 'files', which produces
126 a section containing a list of files. Process these entries so that
127 this information is added to the device tree.
129 super().ExpandEntries()
130 for entry in self._entries.values():
131 entry.ExpandEntries()
133 def AddMissingProperties(self):
134 """Add new properties to the device tree as needed for this entry"""
135 super().AddMissingProperties()
136 for entry in self._entries.values():
137 entry.AddMissingProperties()
139 def ObtainContents(self):
140 return self.GetEntryContents()
145 for entry in self._entries.values():
146 data = entry.GetData()
147 base = self.pad_before + (entry.offset or 0) - self._skip_at_start
148 pad = base - len(section_data)
150 section_data += tools.GetBytes(self._pad_byte, pad)
153 pad = self.size - len(section_data)
155 section_data += tools.GetBytes(self._pad_byte, pad)
156 self.Detail('GetData: %d entries, total size %#x' %
157 (len(self._entries), len(section_data)))
160 def GetOffsets(self):
161 """Handle entries that want to set the offset/size of other entries
163 This calls each entry's GetOffsets() method. If it returns a list
164 of entries to update, it updates them.
166 self.GetEntryOffsets()
169 def ResetForPack(self):
170 """Reset offset/size fields so that packing can be done again"""
171 super().ResetForPack()
172 for entry in self._entries.values():
175 def Pack(self, offset):
176 """Pack all entries into the section"""
178 return super().Pack(offset)
180 def _PackEntries(self):
181 """Pack all entries into the image"""
182 offset = self._skip_at_start
183 for entry in self._entries.values():
184 offset = entry.Pack(offset)
185 self.size = self.CheckSize()
187 def _ExpandEntries(self):
188 """Expand any entries that are permitted to"""
190 for entry in self._entries.values():
192 exp_entry.ExpandToLimit(entry.offset)
194 if entry.expand_size:
197 exp_entry.ExpandToLimit(self.size)
199 def _SortEntries(self):
200 """Sort entries by offset"""
201 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
202 self._entries.clear()
203 for entry in entries:
204 self._entries[entry._node.name] = entry
206 def CheckEntries(self):
207 """Check that entries do not overlap or extend outside the image"""
210 self._ExpandEntries()
213 for entry in self._entries.values():
215 if (entry.offset < self._skip_at_start or
216 entry.offset + entry.size > self._skip_at_start +
218 entry.Raise("Offset %#x (%d) is outside the section starting "
220 (entry.offset, entry.offset, self._skip_at_start,
221 self._skip_at_start))
222 if entry.offset < offset:
223 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
224 "ending at %#x (%d)" %
225 (entry.offset, entry.offset, prev_name, offset, offset))
226 offset = entry.offset + entry.size
227 prev_name = entry.GetPath()
229 def WriteSymbols(self, section):
230 """Write symbol values into binary files for access at run time"""
231 for entry in self._entries.values():
232 entry.WriteSymbols(self)
234 def SetCalculatedProperties(self):
235 super().SetCalculatedProperties()
236 for entry in self._entries.values():
237 entry.SetCalculatedProperties()
239 def SetImagePos(self, image_pos):
240 super().SetImagePos(image_pos)
241 for entry in self._entries.values():
242 entry.SetImagePos(image_pos + self.offset)
244 def ProcessContents(self):
245 sizes_ok_base = super(Entry_section, self).ProcessContents()
247 for entry in self._entries.values():
248 if not entry.ProcessContents():
250 return sizes_ok and sizes_ok_base
252 def CheckOffset(self):
255 def WriteMap(self, fd, indent):
256 """Write a map of the section to a .map file
259 fd: File to write the map to
261 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
262 self.size, self.image_pos)
263 for entry in self._entries.values():
264 entry.WriteMap(fd, indent + 1)
266 def GetEntries(self):
269 def GetContentsByPhandle(self, phandle, source_entry):
270 """Get the data contents of an entry specified by a phandle
272 This uses a phandle to look up a node and and find the entry
273 associated with it. Then it returnst he contents of that entry.
276 phandle: Phandle to look up (integer)
277 source_entry: Entry containing that phandle (used for error
281 data from associated entry (as a string), or None if not found
283 node = self._node.GetFdt().LookupPhandle(phandle)
285 source_entry.Raise("Cannot find node for phandle %d" % phandle)
286 for entry in self._entries.values():
287 if entry._node == node:
288 return entry.GetData()
289 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
291 def LookupSymbol(self, sym_name, optional, msg, base_addr):
292 """Look up a symbol in an ELF file
294 Looks up a symbol in an ELF file. Only entry types which come from an
295 ELF image can be used by this function.
297 At present the only entry properties supported are:
299 image_pos - 'base_addr' is added if this is not an end-at-4gb image
303 sym_name: Symbol name in the ELF file to look up in the format
304 _binman_<entry>_prop_<property> where <entry> is the name of
305 the entry and <property> is the property to find (e.g.
306 _binman_u_boot_prop_offset). As a special case, you can append
307 _any to <entry> to have it search for any matching entry. E.g.
308 _binman_u_boot_any_prop_offset will match entries called u-boot,
309 u-boot-img and u-boot-nodtb)
310 optional: True if the symbol is optional. If False this function
311 will raise if the symbol is not found
312 msg: Message to display if an error occurs
313 base_addr: Base address of image. This is added to the returned
314 image_pos in most cases so that the returned position indicates
315 where the targetted entry/binary has actually been loaded. But
316 if end-at-4gb is used, this is not done, since the binary is
317 already assumed to be linked to the ROM position and using
318 execute-in-place (XIP).
321 Value that should be assigned to that symbol, or None if it was
322 optional and not found
325 ValueError if the symbol is invalid or not found, or references a
326 property which is not supported
328 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
330 raise ValueError("%s: Symbol '%s' has invalid format" %
332 entry_name, prop_name = m.groups()
333 entry_name = entry_name.replace('_', '-')
334 entry = self._entries.get(entry_name)
336 if entry_name.endswith('-any'):
337 root = entry_name[:-4]
338 for name in self._entries:
339 if name.startswith(root):
340 rest = name[len(root):]
341 if rest in ['', '-img', '-nodtb']:
342 entry = self._entries[name]
344 err = ("%s: Entry '%s' not found in list (%s)" %
345 (msg, entry_name, ','.join(self._entries.keys())))
347 print('Warning: %s' % err, file=sys.stderr)
349 raise ValueError(err)
350 if prop_name == 'offset':
352 elif prop_name == 'image_pos':
353 value = entry.image_pos
354 if not self.GetImage()._end_4gb:
357 if prop_name == 'size':
360 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
362 def GetRootSkipAtStart(self):
363 """Get the skip-at-start value for the top-level section
365 This is used to find out the starting offset for root section that
366 contains this section. If this is a top-level section then it returns
367 the skip-at-start offset for this section.
369 This is used to get the absolute position of section within the image.
372 Integer skip-at-start value for the root section containing this
376 return self.section.GetRootSkipAtStart()
377 return self._skip_at_start
379 def GetStartOffset(self):
380 """Get the start offset for this section
383 The first available offset in this section (typically 0)
385 return self._skip_at_start
387 def GetImageSize(self):
388 """Get the size of the image containing this section
391 Image size as an integer number of bytes, which may be None if the
392 image size is dynamic and its sections have not yet been packed
394 return self.GetImage().size
396 def FindEntryType(self, etype):
397 """Find an entry type in the section
400 etype: Entry type to find
402 entry matching that type, or None if not found
404 for entry in self._entries.values():
405 if entry.etype == etype:
409 def GetEntryContents(self):
410 """Call ObtainContents() for the section
412 todo = self._entries.values()
413 for passnum in range(3):
416 if not entry.ObtainContents():
417 next_todo.append(entry)
422 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
426 def _SetEntryOffsetSize(self, name, offset, size):
427 """Set the offset and size of an entry
430 name: Entry name to update
431 offset: New offset, or None to leave alone
432 size: New size, or None to leave alone
434 entry = self._entries.get(name)
436 self._Raise("Unable to set offset/size for unknown entry '%s'" %
438 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
441 def GetEntryOffsets(self):
442 """Handle entries that want to set the offset/size of other entries
444 This calls each entry's GetOffsets() method. If it returns a list
445 of entries to update, it updates them.
447 for entry in self._entries.values():
448 offset_dict = entry.GetOffsets()
449 for name, info in offset_dict.items():
450 self._SetEntryOffsetSize(name, *info)
454 """Check that the image contents does not exceed its size, etc."""
456 for entry in self._entries.values():
457 contents_size = max(contents_size, entry.offset + entry.size)
459 contents_size -= self._skip_at_start
463 size = self.pad_before + contents_size + self.pad_after
464 size = tools.Align(size, self.align_size)
466 if self.size and contents_size > self.size:
467 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
468 (contents_size, contents_size, self.size, self.size))
471 if self.size != tools.Align(self.size, self.align_size):
472 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
473 (self.size, self.size, self.align_size,
477 def ListEntries(self, entries, indent):
478 """List the files in the section"""
479 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
480 self.image_pos, None, self.offset, self)
481 for entry in self._entries.values():
482 entry.ListEntries(entries, indent + 1)
484 def LoadData(self, decomp=True):
485 for entry in self._entries.values():
486 entry.LoadData(decomp)
487 self.Detail('Loaded data')
490 """Get the image containing this section
492 Note that a top-level section is actually an Image, so this function may
496 Image object containing this section
500 return self.section.GetImage()
503 """Check if the entries in this section will be sorted
506 True if to be sorted, False if entries will be left in the order
507 they appear in the device tree
511 def ReadData(self, decomp=True):
512 tout.Info("ReadData path='%s'" % self.GetPath())
513 parent_data = self.section.ReadData(True)
514 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
515 (self.GetPath(), self.offset, self.offset + self.size,
517 data = parent_data[self.offset:self.offset + self.size]
520 def ReadChildData(self, child, decomp=True):
521 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
522 parent_data = self.ReadData(True)
523 offset = child.offset - self._skip_at_start
524 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
525 (child.GetPath(), child.offset, self._skip_at_start, offset))
526 data = parent_data[offset:offset + child.size]
529 data = tools.Decompress(indata, child.compress)
530 if child.uncomp_size:
531 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
532 (child.GetPath(), len(indata), child.compress,
536 def WriteChildData(self, child):