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 section 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()
147 def _BuildSectionData(self):
148 """Build the contents of a section
150 This places all entries at the right place, dealing with padding before
151 and after entries. It does not do padding for the section itself (the
152 pad-before and pad-after properties in the section items) since that is
153 handled by the parent section.
156 Contents of the section (bytes)
160 for entry in self._entries.values():
161 data = entry.GetData()
162 # Handle empty space before the entry
163 pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
165 section_data += tools.GetBytes(self._pad_byte, pad)
167 # Handle padding before the entry
169 section_data += tools.GetBytes(self._pad_byte, entry.pad_before)
171 # Add in the actual entry data
174 # Handle padding after the entry
176 section_data += tools.GetBytes(self._pad_byte, entry.pad_after)
179 section_data += tools.GetBytes(self._pad_byte,
180 self.size - len(section_data))
181 self.Detail('GetData: %d entries, total size %#x' %
182 (len(self._entries), len(section_data)))
183 return self.CompressData(section_data)
186 return self._BuildSectionData()
188 def GetOffsets(self):
189 """Handle entries that want to set the offset/size of other entries
191 This calls each entry's GetOffsets() method. If it returns a list
192 of entries to update, it updates them.
194 self.GetEntryOffsets()
197 def ResetForPack(self):
198 """Reset offset/size fields so that packing can be done again"""
199 super().ResetForPack()
200 for entry in self._entries.values():
203 def Pack(self, offset):
204 """Pack all entries into the section"""
206 return super().Pack(offset)
208 def _PackEntries(self):
209 """Pack all entries into the section"""
210 offset = self._skip_at_start
211 for entry in self._entries.values():
212 offset = entry.Pack(offset)
213 self.size = self.CheckSize()
215 def _ExpandEntries(self):
216 """Expand any entries that are permitted to"""
218 for entry in self._entries.values():
220 exp_entry.ExpandToLimit(entry.offset)
222 if entry.expand_size:
225 exp_entry.ExpandToLimit(self.size)
227 def _SortEntries(self):
228 """Sort entries by offset"""
229 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
230 self._entries.clear()
231 for entry in entries:
232 self._entries[entry._node.name] = entry
234 def CheckEntries(self):
235 """Check that entries do not overlap or extend outside the section"""
238 self._ExpandEntries()
241 for entry in self._entries.values():
243 if (entry.offset < self._skip_at_start or
244 entry.offset + entry.size > self._skip_at_start +
246 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
247 "section '%s' starting at %#x (%d) "
249 (entry.offset, entry.offset, entry.size, entry.size,
250 self._node.path, self._skip_at_start,
251 self._skip_at_start, self.size, self.size))
252 if entry.offset < offset and entry.size:
253 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
254 "ending at %#x (%d)" %
255 (entry.offset, entry.offset, prev_name, offset, offset))
256 offset = entry.offset + entry.size
257 prev_name = entry.GetPath()
259 def WriteSymbols(self, section):
260 """Write symbol values into binary files for access at run time"""
261 for entry in self._entries.values():
262 entry.WriteSymbols(self)
264 def SetCalculatedProperties(self):
265 super().SetCalculatedProperties()
266 for entry in self._entries.values():
267 entry.SetCalculatedProperties()
269 def SetImagePos(self, image_pos):
270 super().SetImagePos(image_pos)
271 for entry in self._entries.values():
272 entry.SetImagePos(image_pos + self.offset)
274 def ProcessContents(self):
275 sizes_ok_base = super(Entry_section, self).ProcessContents()
277 for entry in self._entries.values():
278 if not entry.ProcessContents():
280 return sizes_ok and sizes_ok_base
282 def CheckOffset(self):
285 def WriteMap(self, fd, indent):
286 """Write a map of the section to a .map file
289 fd: File to write the map to
291 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
292 self.size, self.image_pos)
293 for entry in self._entries.values():
294 entry.WriteMap(fd, indent + 1)
296 def GetEntries(self):
299 def GetContentsByPhandle(self, phandle, source_entry):
300 """Get the data contents of an entry specified by a phandle
302 This uses a phandle to look up a node and and find the entry
303 associated with it. Then it returnst he contents of that entry.
306 phandle: Phandle to look up (integer)
307 source_entry: Entry containing that phandle (used for error
311 data from associated entry (as a string), or None if not found
313 node = self._node.GetFdt().LookupPhandle(phandle)
315 source_entry.Raise("Cannot find node for phandle %d" % phandle)
316 for entry in self._entries.values():
317 if entry._node == node:
318 return entry.GetData()
319 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
321 def LookupSymbol(self, sym_name, optional, msg, base_addr):
322 """Look up a symbol in an ELF file
324 Looks up a symbol in an ELF file. Only entry types which come from an
325 ELF image can be used by this function.
327 At present the only entry properties supported are:
329 image_pos - 'base_addr' is added if this is not an end-at-4gb image
333 sym_name: Symbol name in the ELF file to look up in the format
334 _binman_<entry>_prop_<property> where <entry> is the name of
335 the entry and <property> is the property to find (e.g.
336 _binman_u_boot_prop_offset). As a special case, you can append
337 _any to <entry> to have it search for any matching entry. E.g.
338 _binman_u_boot_any_prop_offset will match entries called u-boot,
339 u-boot-img and u-boot-nodtb)
340 optional: True if the symbol is optional. If False this function
341 will raise if the symbol is not found
342 msg: Message to display if an error occurs
343 base_addr: Base address of image. This is added to the returned
344 image_pos in most cases so that the returned position indicates
345 where the targetted entry/binary has actually been loaded. But
346 if end-at-4gb is used, this is not done, since the binary is
347 already assumed to be linked to the ROM position and using
348 execute-in-place (XIP).
351 Value that should be assigned to that symbol, or None if it was
352 optional and not found
355 ValueError if the symbol is invalid or not found, or references a
356 property which is not supported
358 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
360 raise ValueError("%s: Symbol '%s' has invalid format" %
362 entry_name, prop_name = m.groups()
363 entry_name = entry_name.replace('_', '-')
364 entry = self._entries.get(entry_name)
366 if entry_name.endswith('-any'):
367 root = entry_name[:-4]
368 for name in self._entries:
369 if name.startswith(root):
370 rest = name[len(root):]
371 if rest in ['', '-img', '-nodtb']:
372 entry = self._entries[name]
374 err = ("%s: Entry '%s' not found in list (%s)" %
375 (msg, entry_name, ','.join(self._entries.keys())))
377 print('Warning: %s' % err, file=sys.stderr)
379 raise ValueError(err)
380 if prop_name == 'offset':
382 elif prop_name == 'image_pos':
383 value = entry.image_pos
384 if not self.GetImage()._end_4gb:
387 if prop_name == 'size':
390 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
392 def GetRootSkipAtStart(self):
393 """Get the skip-at-start value for the top-level section
395 This is used to find out the starting offset for root section that
396 contains this section. If this is a top-level section then it returns
397 the skip-at-start offset for this section.
399 This is used to get the absolute position of section within the image.
402 Integer skip-at-start value for the root section containing this
406 return self.section.GetRootSkipAtStart()
407 return self._skip_at_start
409 def GetStartOffset(self):
410 """Get the start offset for this section
413 The first available offset in this section (typically 0)
415 return self._skip_at_start
417 def GetImageSize(self):
418 """Get the size of the image containing this section
421 Image size as an integer number of bytes, which may be None if the
422 image size is dynamic and its sections have not yet been packed
424 return self.GetImage().size
426 def FindEntryType(self, etype):
427 """Find an entry type in the section
430 etype: Entry type to find
432 entry matching that type, or None if not found
434 for entry in self._entries.values():
435 if entry.etype == etype:
439 def GetEntryContents(self):
440 """Call ObtainContents() for each entry in the section
442 todo = self._entries.values()
443 for passnum in range(3):
446 if not entry.ObtainContents():
447 next_todo.append(entry)
452 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
456 def _SetEntryOffsetSize(self, name, offset, size):
457 """Set the offset and size of an entry
460 name: Entry name to update
461 offset: New offset, or None to leave alone
462 size: New size, or None to leave alone
464 entry = self._entries.get(name)
466 self._Raise("Unable to set offset/size for unknown entry '%s'" %
468 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
471 def GetEntryOffsets(self):
472 """Handle entries that want to set the offset/size of other entries
474 This calls each entry's GetOffsets() method. If it returns a list
475 of entries to update, it updates them.
477 for entry in self._entries.values():
478 offset_dict = entry.GetOffsets()
479 for name, info in offset_dict.items():
480 self._SetEntryOffsetSize(name, *info)
484 """Check that the section contents does not exceed its size, etc."""
486 for entry in self._entries.values():
487 contents_size = max(contents_size, entry.offset + entry.size)
489 contents_size -= self._skip_at_start
493 size = self.pad_before + contents_size + self.pad_after
494 size = tools.Align(size, self.align_size)
496 if self.size and contents_size > self.size:
497 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
498 (contents_size, contents_size, self.size, self.size))
501 if self.size != tools.Align(self.size, self.align_size):
502 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
503 (self.size, self.size, self.align_size,
507 def ListEntries(self, entries, indent):
508 """List the files in the section"""
509 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
510 self.image_pos, None, self.offset, self)
511 for entry in self._entries.values():
512 entry.ListEntries(entries, indent + 1)
514 def LoadData(self, decomp=True):
515 for entry in self._entries.values():
516 entry.LoadData(decomp)
517 self.Detail('Loaded data')
520 """Get the image containing this section
522 Note that a top-level section is actually an Image, so this function may
526 Image object containing this section
530 return self.section.GetImage()
533 """Check if the entries in this section will be sorted
536 True if to be sorted, False if entries will be left in the order
537 they appear in the device tree
541 def ReadData(self, decomp=True):
542 tout.Info("ReadData path='%s'" % self.GetPath())
543 parent_data = self.section.ReadData(True)
544 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
545 (self.GetPath(), self.offset, self.offset + self.size,
547 data = parent_data[self.offset:self.offset + self.size]
550 def ReadChildData(self, child, decomp=True):
551 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
552 parent_data = self.ReadData(True)
553 offset = child.offset - self._skip_at_start
554 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
555 (child.GetPath(), child.offset, self._skip_at_start, offset))
556 data = parent_data[offset:offset + child.size]
559 data = tools.Decompress(indata, child.compress)
560 if child.uncomp_size:
561 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
562 (child.GetPath(), len(indata), child.compress,
566 def WriteChildData(self, child):
569 def SetAllowMissing(self, allow_missing):
570 """Set whether a section allows missing external blobs
573 allow_missing: True if allowed, False if not allowed
575 self.allow_missing = allow_missing
576 for entry in self._entries.values():
577 entry.SetAllowMissing(allow_missing)
579 def CheckMissing(self, missing_list):
580 """Check if any entries in this section have missing external blobs
582 If there are missing blobs, the entries are added to the list
585 missing_list: List of Entry objects to be added to
587 for entry in self._entries.values():
588 entry.CheckMissing(missing_list)