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
57 self._allow_missing = False
60 """Read properties from the image 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 == 'hash':
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()
151 for entry in self._entries.values():
152 data = entry.GetData()
153 base = self.pad_before + (entry.offset or 0) - self._skip_at_start
154 pad = base - len(section_data)
156 section_data += tools.GetBytes(self._pad_byte, pad)
159 pad = self.size - len(section_data)
161 section_data += tools.GetBytes(self._pad_byte, pad)
162 self.Detail('GetData: %d entries, total size %#x' %
163 (len(self._entries), len(section_data)))
166 def GetOffsets(self):
167 """Handle entries that want to set the offset/size of other entries
169 This calls each entry's GetOffsets() method. If it returns a list
170 of entries to update, it updates them.
172 self.GetEntryOffsets()
175 def ResetForPack(self):
176 """Reset offset/size fields so that packing can be done again"""
177 super().ResetForPack()
178 for entry in self._entries.values():
181 def Pack(self, offset):
182 """Pack all entries into the section"""
184 return super().Pack(offset)
186 def _PackEntries(self):
187 """Pack all entries into the image"""
188 offset = self._skip_at_start
189 for entry in self._entries.values():
190 offset = entry.Pack(offset)
191 self.size = self.CheckSize()
193 def _ExpandEntries(self):
194 """Expand any entries that are permitted to"""
196 for entry in self._entries.values():
198 exp_entry.ExpandToLimit(entry.offset)
200 if entry.expand_size:
203 exp_entry.ExpandToLimit(self.size)
205 def _SortEntries(self):
206 """Sort entries by offset"""
207 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
208 self._entries.clear()
209 for entry in entries:
210 self._entries[entry._node.name] = entry
212 def CheckEntries(self):
213 """Check that entries do not overlap or extend outside the image"""
216 self._ExpandEntries()
219 for entry in self._entries.values():
221 if (entry.offset < self._skip_at_start or
222 entry.offset + entry.size > self._skip_at_start +
224 entry.Raise("Offset %#x (%d) is outside the section starting "
226 (entry.offset, entry.offset, self._skip_at_start,
227 self._skip_at_start))
228 if entry.offset < offset:
229 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
230 "ending at %#x (%d)" %
231 (entry.offset, entry.offset, prev_name, offset, offset))
232 offset = entry.offset + entry.size
233 prev_name = entry.GetPath()
235 def WriteSymbols(self, section):
236 """Write symbol values into binary files for access at run time"""
237 for entry in self._entries.values():
238 entry.WriteSymbols(self)
240 def SetCalculatedProperties(self):
241 super().SetCalculatedProperties()
242 for entry in self._entries.values():
243 entry.SetCalculatedProperties()
245 def SetImagePos(self, image_pos):
246 super().SetImagePos(image_pos)
247 for entry in self._entries.values():
248 entry.SetImagePos(image_pos + self.offset)
250 def ProcessContents(self):
251 sizes_ok_base = super(Entry_section, self).ProcessContents()
253 for entry in self._entries.values():
254 if not entry.ProcessContents():
256 return sizes_ok and sizes_ok_base
258 def CheckOffset(self):
261 def WriteMap(self, fd, indent):
262 """Write a map of the section to a .map file
265 fd: File to write the map to
267 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
268 self.size, self.image_pos)
269 for entry in self._entries.values():
270 entry.WriteMap(fd, indent + 1)
272 def GetEntries(self):
275 def GetContentsByPhandle(self, phandle, source_entry):
276 """Get the data contents of an entry specified by a phandle
278 This uses a phandle to look up a node and and find the entry
279 associated with it. Then it returnst he contents of that entry.
282 phandle: Phandle to look up (integer)
283 source_entry: Entry containing that phandle (used for error
287 data from associated entry (as a string), or None if not found
289 node = self._node.GetFdt().LookupPhandle(phandle)
291 source_entry.Raise("Cannot find node for phandle %d" % phandle)
292 for entry in self._entries.values():
293 if entry._node == node:
294 return entry.GetData()
295 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
297 def LookupSymbol(self, sym_name, optional, msg, base_addr):
298 """Look up a symbol in an ELF file
300 Looks up a symbol in an ELF file. Only entry types which come from an
301 ELF image can be used by this function.
303 At present the only entry properties supported are:
305 image_pos - 'base_addr' is added if this is not an end-at-4gb image
309 sym_name: Symbol name in the ELF file to look up in the format
310 _binman_<entry>_prop_<property> where <entry> is the name of
311 the entry and <property> is the property to find (e.g.
312 _binman_u_boot_prop_offset). As a special case, you can append
313 _any to <entry> to have it search for any matching entry. E.g.
314 _binman_u_boot_any_prop_offset will match entries called u-boot,
315 u-boot-img and u-boot-nodtb)
316 optional: True if the symbol is optional. If False this function
317 will raise if the symbol is not found
318 msg: Message to display if an error occurs
319 base_addr: Base address of image. This is added to the returned
320 image_pos in most cases so that the returned position indicates
321 where the targetted entry/binary has actually been loaded. But
322 if end-at-4gb is used, this is not done, since the binary is
323 already assumed to be linked to the ROM position and using
324 execute-in-place (XIP).
327 Value that should be assigned to that symbol, or None if it was
328 optional and not found
331 ValueError if the symbol is invalid or not found, or references a
332 property which is not supported
334 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
336 raise ValueError("%s: Symbol '%s' has invalid format" %
338 entry_name, prop_name = m.groups()
339 entry_name = entry_name.replace('_', '-')
340 entry = self._entries.get(entry_name)
342 if entry_name.endswith('-any'):
343 root = entry_name[:-4]
344 for name in self._entries:
345 if name.startswith(root):
346 rest = name[len(root):]
347 if rest in ['', '-img', '-nodtb']:
348 entry = self._entries[name]
350 err = ("%s: Entry '%s' not found in list (%s)" %
351 (msg, entry_name, ','.join(self._entries.keys())))
353 print('Warning: %s' % err, file=sys.stderr)
355 raise ValueError(err)
356 if prop_name == 'offset':
358 elif prop_name == 'image_pos':
359 value = entry.image_pos
360 if not self.GetImage()._end_4gb:
363 if prop_name == 'size':
366 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
368 def GetRootSkipAtStart(self):
369 """Get the skip-at-start value for the top-level section
371 This is used to find out the starting offset for root section that
372 contains this section. If this is a top-level section then it returns
373 the skip-at-start offset for this section.
375 This is used to get the absolute position of section within the image.
378 Integer skip-at-start value for the root section containing this
382 return self.section.GetRootSkipAtStart()
383 return self._skip_at_start
385 def GetStartOffset(self):
386 """Get the start offset for this section
389 The first available offset in this section (typically 0)
391 return self._skip_at_start
393 def GetImageSize(self):
394 """Get the size of the image containing this section
397 Image size as an integer number of bytes, which may be None if the
398 image size is dynamic and its sections have not yet been packed
400 return self.GetImage().size
402 def FindEntryType(self, etype):
403 """Find an entry type in the section
406 etype: Entry type to find
408 entry matching that type, or None if not found
410 for entry in self._entries.values():
411 if entry.etype == etype:
415 def GetEntryContents(self):
416 """Call ObtainContents() for the section
418 todo = self._entries.values()
419 for passnum in range(3):
422 if not entry.ObtainContents():
423 next_todo.append(entry)
428 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
432 def _SetEntryOffsetSize(self, name, offset, size):
433 """Set the offset and size of an entry
436 name: Entry name to update
437 offset: New offset, or None to leave alone
438 size: New size, or None to leave alone
440 entry = self._entries.get(name)
442 self._Raise("Unable to set offset/size for unknown entry '%s'" %
444 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
447 def GetEntryOffsets(self):
448 """Handle entries that want to set the offset/size of other entries
450 This calls each entry's GetOffsets() method. If it returns a list
451 of entries to update, it updates them.
453 for entry in self._entries.values():
454 offset_dict = entry.GetOffsets()
455 for name, info in offset_dict.items():
456 self._SetEntryOffsetSize(name, *info)
460 """Check that the image contents does not exceed its size, etc."""
462 for entry in self._entries.values():
463 contents_size = max(contents_size, entry.offset + entry.size)
465 contents_size -= self._skip_at_start
469 size = self.pad_before + contents_size + self.pad_after
470 size = tools.Align(size, self.align_size)
472 if self.size and contents_size > self.size:
473 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
474 (contents_size, contents_size, self.size, self.size))
477 if self.size != tools.Align(self.size, self.align_size):
478 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
479 (self.size, self.size, self.align_size,
483 def ListEntries(self, entries, indent):
484 """List the files in the section"""
485 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
486 self.image_pos, None, self.offset, self)
487 for entry in self._entries.values():
488 entry.ListEntries(entries, indent + 1)
490 def LoadData(self, decomp=True):
491 for entry in self._entries.values():
492 entry.LoadData(decomp)
493 self.Detail('Loaded data')
496 """Get the image containing this section
498 Note that a top-level section is actually an Image, so this function may
502 Image object containing this section
506 return self.section.GetImage()
509 """Check if the entries in this section will be sorted
512 True if to be sorted, False if entries will be left in the order
513 they appear in the device tree
517 def ReadData(self, decomp=True):
518 tout.Info("ReadData path='%s'" % self.GetPath())
519 parent_data = self.section.ReadData(True)
520 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
521 (self.GetPath(), self.offset, self.offset + self.size,
523 data = parent_data[self.offset:self.offset + self.size]
526 def ReadChildData(self, child, decomp=True):
527 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
528 parent_data = self.ReadData(True)
529 offset = child.offset - self._skip_at_start
530 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
531 (child.GetPath(), child.offset, self._skip_at_start, offset))
532 data = parent_data[offset:offset + child.size]
535 data = tools.Decompress(indata, child.compress)
536 if child.uncomp_size:
537 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
538 (child.GetPath(), len(indata), child.compress,
542 def WriteChildData(self, child):
545 def SetAllowMissing(self, allow_missing):
546 """Set whether a section allows missing external blobs
549 allow_missing: True if allowed, False if not allowed
551 self._allow_missing = allow_missing
552 for entry in self._entries.values():
553 entry.SetAllowMissing(allow_missing)
555 def GetAllowMissing(self):
556 """Get whether a section allows missing external blobs
559 True if allowed, False if not allowed
561 return self._allow_missing