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
19 from patman.tools import ToHexSize
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
39 allow_missing: True if this section permits external blobs to be
40 missing their contents. The second will produce an image but of
41 course it will not work.
43 Since a section is also an entry, it inherits all the properies of entries
46 A section is an entry which can contain other entries, thus allowing
47 hierarchical images to be created. See 'Sections and hierarchical images'
48 in the binman README for more information.
50 def __init__(self, section, etype, node, test=False):
52 super().__init__(section, etype, node)
53 self._entries = OrderedDict()
56 self._skip_at_start = None
60 """Read properties from the section node"""
62 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
63 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
64 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
65 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
68 self.Raise("Section size must be provided when using end-at-4gb")
69 if self._skip_at_start is not None:
70 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
72 self._skip_at_start = 0x100000000 - self.size
74 if self._skip_at_start is None:
75 self._skip_at_start = 0
76 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
77 filename = fdt_util.GetString(self._node, 'filename')
79 self._filename = filename
83 def _ReadEntries(self):
84 for node in self._node.subnodes:
85 if node.name.startswith('hash') or node.name.startswith('signature'):
87 entry = Entry.Create(self, node)
89 entry.SetPrefix(self._name_prefix)
90 self._entries[node.name] = entry
92 def _Raise(self, msg):
93 """Raises an error for this section
96 msg: Error message to use in the raise string
100 raise ValueError("Section '%s': %s" % (self._node.path, msg))
104 for entry in self._entries.values():
105 fdts.update(entry.GetFdts())
108 def ProcessFdt(self, fdt):
109 """Allow entries to adjust the device tree
111 Some entries need to adjust the device tree for their purposes. This
112 may involve adding or deleting properties.
114 todo = self._entries.values()
115 for passnum in range(3):
118 if not entry.ProcessFdt(fdt):
119 next_todo.append(entry)
124 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
128 def ExpandEntries(self):
129 """Expand out any entries which have calculated sub-entries
131 Some entries are expanded out at runtime, e.g. 'files', which produces
132 a section containing a list of files. Process these entries so that
133 this information is added to the device tree.
135 super().ExpandEntries()
136 for entry in self._entries.values():
137 entry.ExpandEntries()
139 def AddMissingProperties(self):
140 """Add new properties to the device tree as needed for this entry"""
141 super().AddMissingProperties()
142 for entry in self._entries.values():
143 entry.AddMissingProperties()
145 def ObtainContents(self):
146 return self.GetEntryContents()
148 def GetPaddedDataForEntry(self, entry):
149 """Get the data for an entry including any padding
151 Gets the entry data and uses the section pad-byte value to add padding
152 before and after as defined by the pad-before and pad-after properties.
153 This does not consider alignment.
156 entry: Entry to check
159 Contents of the entry along with any pad bytes before and
162 pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
166 # Handle padding before the entry
168 data += tools.GetBytes(self._pad_byte, entry.pad_before)
170 # Add in the actual entry data
171 data += entry.GetData()
173 # Handle padding after the entry
175 data += tools.GetBytes(self._pad_byte, entry.pad_after)
178 data += tools.GetBytes(pad_byte, entry.size - len(data))
180 self.Detail('GetPaddedDataForEntry: size %s' % ToHexSize(self.data))
184 def _BuildSectionData(self):
185 """Build the contents of a section
187 This places all entries at the right place, dealing with padding before
188 and after entries. It does not do padding for the section itself (the
189 pad-before and pad-after properties in the section items) since that is
190 handled by the parent section.
193 Contents of the section (bytes)
197 for entry in self._entries.values():
198 data = self.GetPaddedDataForEntry(entry)
199 # Handle empty space before the entry
200 pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
202 section_data += tools.GetBytes(self._pad_byte, pad)
204 # Add in the actual entry data
207 self.Detail('GetData: %d entries, total size %#x' %
208 (len(self._entries), len(section_data)))
209 return self.CompressData(section_data)
211 def GetPaddedData(self):
212 """Get the data for a section including any padding
214 Gets the section data and uses the parent section's pad-byte value to
215 add padding before and after as defined by the pad-before and pad-after
216 properties. If this is a top-level section (i.e. an image), this is the
217 same as GetData(), since padding is not supported.
219 This does not consider alignment.
222 Contents of the section along with any pad bytes before and
225 section = self.section or self
226 return section.GetPaddedDataForEntry(self)
229 return self._BuildSectionData()
231 def GetOffsets(self):
232 """Handle entries that want to set the offset/size of other entries
234 This calls each entry's GetOffsets() method. If it returns a list
235 of entries to update, it updates them.
237 self.GetEntryOffsets()
240 def ResetForPack(self):
241 """Reset offset/size fields so that packing can be done again"""
242 super().ResetForPack()
243 for entry in self._entries.values():
246 def Pack(self, offset):
247 """Pack all entries into the section"""
249 return super().Pack(offset)
251 def _PackEntries(self):
252 """Pack all entries into the section"""
253 offset = self._skip_at_start
254 for entry in self._entries.values():
255 offset = entry.Pack(offset)
256 self.size = self.CheckSize()
258 def _ExpandEntries(self):
259 """Expand any entries that are permitted to"""
261 for entry in self._entries.values():
263 exp_entry.ExpandToLimit(entry.offset)
265 if entry.expand_size:
268 exp_entry.ExpandToLimit(self.size)
270 def _SortEntries(self):
271 """Sort entries by offset"""
272 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
273 self._entries.clear()
274 for entry in entries:
275 self._entries[entry._node.name] = entry
277 def CheckEntries(self):
278 """Check that entries do not overlap or extend outside the section"""
281 self._ExpandEntries()
284 for entry in self._entries.values():
286 if (entry.offset < self._skip_at_start or
287 entry.offset + entry.size > self._skip_at_start +
289 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
290 "section '%s' starting at %#x (%d) "
292 (entry.offset, entry.offset, entry.size, entry.size,
293 self._node.path, self._skip_at_start,
294 self._skip_at_start, self.size, self.size))
295 if entry.offset < offset and entry.size:
296 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
297 "ending at %#x (%d)" %
298 (entry.offset, entry.offset, prev_name, offset, offset))
299 offset = entry.offset + entry.size
300 prev_name = entry.GetPath()
302 def WriteSymbols(self, section):
303 """Write symbol values into binary files for access at run time"""
304 for entry in self._entries.values():
305 entry.WriteSymbols(self)
307 def SetCalculatedProperties(self):
308 super().SetCalculatedProperties()
309 for entry in self._entries.values():
310 entry.SetCalculatedProperties()
312 def SetImagePos(self, image_pos):
313 super().SetImagePos(image_pos)
314 for entry in self._entries.values():
315 entry.SetImagePos(image_pos + self.offset)
317 def ProcessContents(self):
318 sizes_ok_base = super(Entry_section, self).ProcessContents()
320 for entry in self._entries.values():
321 if not entry.ProcessContents():
323 return sizes_ok and sizes_ok_base
325 def CheckOffset(self):
328 def WriteMap(self, fd, indent):
329 """Write a map of the section to a .map file
332 fd: File to write the map to
334 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
335 self.size, self.image_pos)
336 for entry in self._entries.values():
337 entry.WriteMap(fd, indent + 1)
339 def GetEntries(self):
342 def GetContentsByPhandle(self, phandle, source_entry):
343 """Get the data contents of an entry specified by a phandle
345 This uses a phandle to look up a node and and find the entry
346 associated with it. Then it returnst he contents of that entry.
349 phandle: Phandle to look up (integer)
350 source_entry: Entry containing that phandle (used for error
354 data from associated entry (as a string), or None if not found
356 node = self._node.GetFdt().LookupPhandle(phandle)
358 source_entry.Raise("Cannot find node for phandle %d" % phandle)
359 for entry in self._entries.values():
360 if entry._node == node:
361 return entry.GetData()
362 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
364 def LookupSymbol(self, sym_name, optional, msg, base_addr):
365 """Look up a symbol in an ELF file
367 Looks up a symbol in an ELF file. Only entry types which come from an
368 ELF image can be used by this function.
370 At present the only entry properties supported are:
372 image_pos - 'base_addr' is added if this is not an end-at-4gb image
376 sym_name: Symbol name in the ELF file to look up in the format
377 _binman_<entry>_prop_<property> where <entry> is the name of
378 the entry and <property> is the property to find (e.g.
379 _binman_u_boot_prop_offset). As a special case, you can append
380 _any to <entry> to have it search for any matching entry. E.g.
381 _binman_u_boot_any_prop_offset will match entries called u-boot,
382 u-boot-img and u-boot-nodtb)
383 optional: True if the symbol is optional. If False this function
384 will raise if the symbol is not found
385 msg: Message to display if an error occurs
386 base_addr: Base address of image. This is added to the returned
387 image_pos in most cases so that the returned position indicates
388 where the targetted entry/binary has actually been loaded. But
389 if end-at-4gb is used, this is not done, since the binary is
390 already assumed to be linked to the ROM position and using
391 execute-in-place (XIP).
394 Value that should be assigned to that symbol, or None if it was
395 optional and not found
398 ValueError if the symbol is invalid or not found, or references a
399 property which is not supported
401 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
403 raise ValueError("%s: Symbol '%s' has invalid format" %
405 entry_name, prop_name = m.groups()
406 entry_name = entry_name.replace('_', '-')
407 entry = self._entries.get(entry_name)
409 if entry_name.endswith('-any'):
410 root = entry_name[:-4]
411 for name in self._entries:
412 if name.startswith(root):
413 rest = name[len(root):]
414 if rest in ['', '-img', '-nodtb']:
415 entry = self._entries[name]
417 err = ("%s: Entry '%s' not found in list (%s)" %
418 (msg, entry_name, ','.join(self._entries.keys())))
420 print('Warning: %s' % err, file=sys.stderr)
422 raise ValueError(err)
423 if prop_name == 'offset':
425 elif prop_name == 'image_pos':
426 value = entry.image_pos
427 if not self.GetImage()._end_4gb:
430 if prop_name == 'size':
433 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
435 def GetRootSkipAtStart(self):
436 """Get the skip-at-start value for the top-level section
438 This is used to find out the starting offset for root section that
439 contains this section. If this is a top-level section then it returns
440 the skip-at-start offset for this section.
442 This is used to get the absolute position of section within the image.
445 Integer skip-at-start value for the root section containing this
449 return self.section.GetRootSkipAtStart()
450 return self._skip_at_start
452 def GetStartOffset(self):
453 """Get the start offset for this section
456 The first available offset in this section (typically 0)
458 return self._skip_at_start
460 def GetImageSize(self):
461 """Get the size of the image containing this section
464 Image size as an integer number of bytes, which may be None if the
465 image size is dynamic and its sections have not yet been packed
467 return self.GetImage().size
469 def FindEntryType(self, etype):
470 """Find an entry type in the section
473 etype: Entry type to find
475 entry matching that type, or None if not found
477 for entry in self._entries.values():
478 if entry.etype == etype:
482 def GetEntryContents(self):
483 """Call ObtainContents() for each entry in the section
485 todo = self._entries.values()
486 for passnum in range(3):
489 if not entry.ObtainContents():
490 next_todo.append(entry)
495 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
499 def _SetEntryOffsetSize(self, name, offset, size):
500 """Set the offset and size of an entry
503 name: Entry name to update
504 offset: New offset, or None to leave alone
505 size: New size, or None to leave alone
507 entry = self._entries.get(name)
509 self._Raise("Unable to set offset/size for unknown entry '%s'" %
511 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
514 def GetEntryOffsets(self):
515 """Handle entries that want to set the offset/size of other entries
517 This calls each entry's GetOffsets() method. If it returns a list
518 of entries to update, it updates them.
520 for entry in self._entries.values():
521 offset_dict = entry.GetOffsets()
522 for name, info in offset_dict.items():
523 self._SetEntryOffsetSize(name, *info)
527 """Check that the section contents does not exceed its size, etc."""
529 for entry in self._entries.values():
530 contents_size = max(contents_size, entry.offset + entry.size)
532 contents_size -= self._skip_at_start
536 size = self.pad_before + contents_size + self.pad_after
537 size = tools.Align(size, self.align_size)
539 if self.size and contents_size > self.size:
540 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
541 (contents_size, contents_size, self.size, self.size))
544 if self.size != tools.Align(self.size, self.align_size):
545 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
546 (self.size, self.size, self.align_size,
550 def ListEntries(self, entries, indent):
551 """List the files in the section"""
552 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
553 self.image_pos, None, self.offset, self)
554 for entry in self._entries.values():
555 entry.ListEntries(entries, indent + 1)
557 def LoadData(self, decomp=True):
558 for entry in self._entries.values():
559 entry.LoadData(decomp)
560 self.Detail('Loaded data')
563 """Get the image containing this section
565 Note that a top-level section is actually an Image, so this function may
569 Image object containing this section
573 return self.section.GetImage()
576 """Check if the entries in this section will be sorted
579 True if to be sorted, False if entries will be left in the order
580 they appear in the device tree
584 def ReadData(self, decomp=True):
585 tout.Info("ReadData path='%s'" % self.GetPath())
586 parent_data = self.section.ReadData(True)
587 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
588 (self.GetPath(), self.offset, self.offset + self.size,
590 data = parent_data[self.offset:self.offset + self.size]
593 def ReadChildData(self, child, decomp=True):
594 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
595 parent_data = self.ReadData(True)
596 offset = child.offset - self._skip_at_start
597 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
598 (child.GetPath(), child.offset, self._skip_at_start, offset))
599 data = parent_data[offset:offset + child.size]
602 data = tools.Decompress(indata, child.compress)
603 if child.uncomp_size:
604 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
605 (child.GetPath(), len(indata), child.compress,
609 def WriteChildData(self, child):
612 def SetAllowMissing(self, allow_missing):
613 """Set whether a section allows missing external blobs
616 allow_missing: True if allowed, False if not allowed
618 self.allow_missing = allow_missing
619 for entry in self._entries.values():
620 entry.SetAllowMissing(allow_missing)
622 def CheckMissing(self, missing_list):
623 """Check if any entries in this section have missing external blobs
625 If there are missing blobs, the entries are added to the list
628 missing_list: List of Entry objects to be added to
630 for entry in self._entries.values():
631 entry.CheckMissing(missing_list)