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
38 allow_missing: True if this section permits external blobs to be
39 missing their contents. The second will produce an image but of
40 course it will not work.
42 Since a section is also an entry, it inherits all the properies of entries
45 A section is an entry which can contain other entries, thus allowing
46 hierarchical images to be created. See 'Sections and hierarchical images'
47 in the binman README for more information.
49 def __init__(self, section, etype, node, test=False):
51 super().__init__(section, etype, node)
52 self._entries = OrderedDict()
55 self._skip_at_start = None
59 """Read properties from the image node"""
61 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
62 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
63 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
64 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
67 self.Raise("Section size must be provided when using end-at-4gb")
68 if self._skip_at_start is not None:
69 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
71 self._skip_at_start = 0x100000000 - self.size
73 if self._skip_at_start is None:
74 self._skip_at_start = 0
75 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
76 filename = fdt_util.GetString(self._node, 'filename')
78 self._filename = filename
82 def _ReadEntries(self):
83 for node in self._node.subnodes:
84 if node.name.startswith('hash') or node.name.startswith('signature'):
86 entry = Entry.Create(self, node)
88 entry.SetPrefix(self._name_prefix)
89 self._entries[node.name] = entry
91 def _Raise(self, msg):
92 """Raises an error for this section
95 msg: Error message to use in the raise string
99 raise ValueError("Section '%s': %s" % (self._node.path, msg))
103 for entry in self._entries.values():
104 fdts.update(entry.GetFdts())
107 def ProcessFdt(self, fdt):
108 """Allow entries to adjust the device tree
110 Some entries need to adjust the device tree for their purposes. This
111 may involve adding or deleting properties.
113 todo = self._entries.values()
114 for passnum in range(3):
117 if not entry.ProcessFdt(fdt):
118 next_todo.append(entry)
123 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
127 def ExpandEntries(self):
128 """Expand out any entries which have calculated sub-entries
130 Some entries are expanded out at runtime, e.g. 'files', which produces
131 a section containing a list of files. Process these entries so that
132 this information is added to the device tree.
134 super().ExpandEntries()
135 for entry in self._entries.values():
136 entry.ExpandEntries()
138 def AddMissingProperties(self):
139 """Add new properties to the device tree as needed for this entry"""
140 super().AddMissingProperties()
141 for entry in self._entries.values():
142 entry.AddMissingProperties()
144 def ObtainContents(self):
145 return self.GetEntryContents()
150 for entry in self._entries.values():
151 data = entry.GetData()
152 base = self.pad_before + (entry.offset or 0) - self._skip_at_start
153 pad = base - len(section_data) + (entry.pad_before or 0)
155 section_data += tools.GetBytes(self._pad_byte, pad)
158 pad = self.size - len(section_data)
160 section_data += tools.GetBytes(self._pad_byte, pad)
161 self.Detail('GetData: %d entries, total size %#x' %
162 (len(self._entries), len(section_data)))
165 def GetOffsets(self):
166 """Handle entries that want to set the offset/size of other entries
168 This calls each entry's GetOffsets() method. If it returns a list
169 of entries to update, it updates them.
171 self.GetEntryOffsets()
174 def ResetForPack(self):
175 """Reset offset/size fields so that packing can be done again"""
176 super().ResetForPack()
177 for entry in self._entries.values():
180 def Pack(self, offset):
181 """Pack all entries into the section"""
183 return super().Pack(offset)
185 def _PackEntries(self):
186 """Pack all entries into the image"""
187 offset = self._skip_at_start
188 for entry in self._entries.values():
189 offset = entry.Pack(offset)
190 self.size = self.CheckSize()
192 def _ExpandEntries(self):
193 """Expand any entries that are permitted to"""
195 for entry in self._entries.values():
197 exp_entry.ExpandToLimit(entry.offset)
199 if entry.expand_size:
202 exp_entry.ExpandToLimit(self.size)
204 def _SortEntries(self):
205 """Sort entries by offset"""
206 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
207 self._entries.clear()
208 for entry in entries:
209 self._entries[entry._node.name] = entry
211 def CheckEntries(self):
212 """Check that entries do not overlap or extend outside the image"""
215 self._ExpandEntries()
218 for entry in self._entries.values():
220 if (entry.offset < self._skip_at_start or
221 entry.offset + entry.size > self._skip_at_start +
223 entry.Raise("Offset %#x (%d) is outside the section starting "
225 (entry.offset, entry.offset, self._skip_at_start,
226 self._skip_at_start))
227 if entry.offset < offset and entry.size:
228 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
229 "ending at %#x (%d)" %
230 (entry.offset, entry.offset, prev_name, offset, offset))
231 offset = entry.offset + entry.size
232 prev_name = entry.GetPath()
234 def WriteSymbols(self, section):
235 """Write symbol values into binary files for access at run time"""
236 for entry in self._entries.values():
237 entry.WriteSymbols(self)
239 def SetCalculatedProperties(self):
240 super().SetCalculatedProperties()
241 for entry in self._entries.values():
242 entry.SetCalculatedProperties()
244 def SetImagePos(self, image_pos):
245 super().SetImagePos(image_pos)
246 for entry in self._entries.values():
247 entry.SetImagePos(image_pos + self.offset)
249 def ProcessContents(self):
250 sizes_ok_base = super(Entry_section, self).ProcessContents()
252 for entry in self._entries.values():
253 if not entry.ProcessContents():
255 return sizes_ok and sizes_ok_base
257 def CheckOffset(self):
260 def WriteMap(self, fd, indent):
261 """Write a map of the section to a .map file
264 fd: File to write the map to
266 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
267 self.size, self.image_pos)
268 for entry in self._entries.values():
269 entry.WriteMap(fd, indent + 1)
271 def GetEntries(self):
274 def GetContentsByPhandle(self, phandle, source_entry):
275 """Get the data contents of an entry specified by a phandle
277 This uses a phandle to look up a node and and find the entry
278 associated with it. Then it returnst he contents of that entry.
281 phandle: Phandle to look up (integer)
282 source_entry: Entry containing that phandle (used for error
286 data from associated entry (as a string), or None if not found
288 node = self._node.GetFdt().LookupPhandle(phandle)
290 source_entry.Raise("Cannot find node for phandle %d" % phandle)
291 for entry in self._entries.values():
292 if entry._node == node:
293 return entry.GetData()
294 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
296 def LookupSymbol(self, sym_name, optional, msg, base_addr):
297 """Look up a symbol in an ELF file
299 Looks up a symbol in an ELF file. Only entry types which come from an
300 ELF image can be used by this function.
302 At present the only entry properties supported are:
304 image_pos - 'base_addr' is added if this is not an end-at-4gb image
308 sym_name: Symbol name in the ELF file to look up in the format
309 _binman_<entry>_prop_<property> where <entry> is the name of
310 the entry and <property> is the property to find (e.g.
311 _binman_u_boot_prop_offset). As a special case, you can append
312 _any to <entry> to have it search for any matching entry. E.g.
313 _binman_u_boot_any_prop_offset will match entries called u-boot,
314 u-boot-img and u-boot-nodtb)
315 optional: True if the symbol is optional. If False this function
316 will raise if the symbol is not found
317 msg: Message to display if an error occurs
318 base_addr: Base address of image. This is added to the returned
319 image_pos in most cases so that the returned position indicates
320 where the targetted entry/binary has actually been loaded. But
321 if end-at-4gb is used, this is not done, since the binary is
322 already assumed to be linked to the ROM position and using
323 execute-in-place (XIP).
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 value = entry.image_pos
359 if not self.GetImage()._end_4gb:
362 if prop_name == 'size':
365 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
367 def GetRootSkipAtStart(self):
368 """Get the skip-at-start value for the top-level section
370 This is used to find out the starting offset for root section that
371 contains this section. If this is a top-level section then it returns
372 the skip-at-start offset for this section.
374 This is used to get the absolute position of section within the image.
377 Integer skip-at-start value for the root section containing this
381 return self.section.GetRootSkipAtStart()
382 return self._skip_at_start
384 def GetStartOffset(self):
385 """Get the start offset for this section
388 The first available offset in this section (typically 0)
390 return self._skip_at_start
392 def GetImageSize(self):
393 """Get the size of the image containing this section
396 Image size as an integer number of bytes, which may be None if the
397 image size is dynamic and its sections have not yet been packed
399 return self.GetImage().size
401 def FindEntryType(self, etype):
402 """Find an entry type in the section
405 etype: Entry type to find
407 entry matching that type, or None if not found
409 for entry in self._entries.values():
410 if entry.etype == etype:
414 def GetEntryContents(self):
415 """Call ObtainContents() for the section
417 todo = self._entries.values()
418 for passnum in range(3):
421 if not entry.ObtainContents():
422 next_todo.append(entry)
427 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
431 def _SetEntryOffsetSize(self, name, offset, size):
432 """Set the offset and size of an entry
435 name: Entry name to update
436 offset: New offset, or None to leave alone
437 size: New size, or None to leave alone
439 entry = self._entries.get(name)
441 self._Raise("Unable to set offset/size for unknown entry '%s'" %
443 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
446 def GetEntryOffsets(self):
447 """Handle entries that want to set the offset/size of other entries
449 This calls each entry's GetOffsets() method. If it returns a list
450 of entries to update, it updates them.
452 for entry in self._entries.values():
453 offset_dict = entry.GetOffsets()
454 for name, info in offset_dict.items():
455 self._SetEntryOffsetSize(name, *info)
459 """Check that the image contents does not exceed its size, etc."""
461 for entry in self._entries.values():
462 contents_size = max(contents_size, entry.offset + entry.size)
464 contents_size -= self._skip_at_start
468 size = self.pad_before + contents_size + self.pad_after
469 size = tools.Align(size, self.align_size)
471 if self.size and contents_size > self.size:
472 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
473 (contents_size, contents_size, self.size, self.size))
476 if self.size != tools.Align(self.size, self.align_size):
477 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
478 (self.size, self.size, self.align_size,
482 def ListEntries(self, entries, indent):
483 """List the files in the section"""
484 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
485 self.image_pos, None, self.offset, self)
486 for entry in self._entries.values():
487 entry.ListEntries(entries, indent + 1)
489 def LoadData(self, decomp=True):
490 for entry in self._entries.values():
491 entry.LoadData(decomp)
492 self.Detail('Loaded data')
495 """Get the image containing this section
497 Note that a top-level section is actually an Image, so this function may
501 Image object containing this section
505 return self.section.GetImage()
508 """Check if the entries in this section will be sorted
511 True if to be sorted, False if entries will be left in the order
512 they appear in the device tree
516 def ReadData(self, decomp=True):
517 tout.Info("ReadData path='%s'" % self.GetPath())
518 parent_data = self.section.ReadData(True)
519 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
520 (self.GetPath(), self.offset, self.offset + self.size,
522 data = parent_data[self.offset:self.offset + self.size]
525 def ReadChildData(self, child, decomp=True):
526 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
527 parent_data = self.ReadData(True)
528 offset = child.offset - self._skip_at_start
529 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
530 (child.GetPath(), child.offset, self._skip_at_start, offset))
531 data = parent_data[offset:offset + child.size]
534 data = tools.Decompress(indata, child.compress)
535 if child.uncomp_size:
536 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
537 (child.GetPath(), len(indata), child.compress,
541 def WriteChildData(self, child):
544 def SetAllowMissing(self, allow_missing):
545 """Set whether a section allows missing external blobs
548 allow_missing: True if allowed, False if not allowed
550 self.allow_missing = allow_missing
551 for entry in self._entries.values():
552 entry.SetAllowMissing(allow_missing)
554 def CheckMissing(self, missing_list):
555 """Check if any entries in this section have missing external blobs
557 If there are missing blobs, the entries are added to the list
560 missing_list: List of Entry objects to be added to
562 for entry in self._entries.values():
563 entry.CheckMissing(missing_list)