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()
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)))
163 return self.CompressData(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 section"""
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 section"""
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) size %#x (%d) is outside the '
224 "section '%s' starting at %#x (%d) "
226 (entry.offset, entry.offset, entry.size, entry.size,
227 self._node.path, self._skip_at_start,
228 self._skip_at_start, self.size, self.size))
229 if entry.offset < offset and entry.size:
230 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
231 "ending at %#x (%d)" %
232 (entry.offset, entry.offset, prev_name, offset, offset))
233 offset = entry.offset + entry.size
234 prev_name = entry.GetPath()
236 def WriteSymbols(self, section):
237 """Write symbol values into binary files for access at run time"""
238 for entry in self._entries.values():
239 entry.WriteSymbols(self)
241 def SetCalculatedProperties(self):
242 super().SetCalculatedProperties()
243 for entry in self._entries.values():
244 entry.SetCalculatedProperties()
246 def SetImagePos(self, image_pos):
247 super().SetImagePos(image_pos)
248 for entry in self._entries.values():
249 entry.SetImagePos(image_pos + self.offset)
251 def ProcessContents(self):
252 sizes_ok_base = super(Entry_section, self).ProcessContents()
254 for entry in self._entries.values():
255 if not entry.ProcessContents():
257 return sizes_ok and sizes_ok_base
259 def CheckOffset(self):
262 def WriteMap(self, fd, indent):
263 """Write a map of the section to a .map file
266 fd: File to write the map to
268 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
269 self.size, self.image_pos)
270 for entry in self._entries.values():
271 entry.WriteMap(fd, indent + 1)
273 def GetEntries(self):
276 def GetContentsByPhandle(self, phandle, source_entry):
277 """Get the data contents of an entry specified by a phandle
279 This uses a phandle to look up a node and and find the entry
280 associated with it. Then it returnst he contents of that entry.
283 phandle: Phandle to look up (integer)
284 source_entry: Entry containing that phandle (used for error
288 data from associated entry (as a string), or None if not found
290 node = self._node.GetFdt().LookupPhandle(phandle)
292 source_entry.Raise("Cannot find node for phandle %d" % phandle)
293 for entry in self._entries.values():
294 if entry._node == node:
295 return entry.GetData()
296 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
298 def LookupSymbol(self, sym_name, optional, msg, base_addr):
299 """Look up a symbol in an ELF file
301 Looks up a symbol in an ELF file. Only entry types which come from an
302 ELF image can be used by this function.
304 At present the only entry properties supported are:
306 image_pos - 'base_addr' is added if this is not an end-at-4gb image
310 sym_name: Symbol name in the ELF file to look up in the format
311 _binman_<entry>_prop_<property> where <entry> is the name of
312 the entry and <property> is the property to find (e.g.
313 _binman_u_boot_prop_offset). As a special case, you can append
314 _any to <entry> to have it search for any matching entry. E.g.
315 _binman_u_boot_any_prop_offset will match entries called u-boot,
316 u-boot-img and u-boot-nodtb)
317 optional: True if the symbol is optional. If False this function
318 will raise if the symbol is not found
319 msg: Message to display if an error occurs
320 base_addr: Base address of image. This is added to the returned
321 image_pos in most cases so that the returned position indicates
322 where the targetted entry/binary has actually been loaded. But
323 if end-at-4gb is used, this is not done, since the binary is
324 already assumed to be linked to the ROM position and using
325 execute-in-place (XIP).
328 Value that should be assigned to that symbol, or None if it was
329 optional and not found
332 ValueError if the symbol is invalid or not found, or references a
333 property which is not supported
335 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
337 raise ValueError("%s: Symbol '%s' has invalid format" %
339 entry_name, prop_name = m.groups()
340 entry_name = entry_name.replace('_', '-')
341 entry = self._entries.get(entry_name)
343 if entry_name.endswith('-any'):
344 root = entry_name[:-4]
345 for name in self._entries:
346 if name.startswith(root):
347 rest = name[len(root):]
348 if rest in ['', '-img', '-nodtb']:
349 entry = self._entries[name]
351 err = ("%s: Entry '%s' not found in list (%s)" %
352 (msg, entry_name, ','.join(self._entries.keys())))
354 print('Warning: %s' % err, file=sys.stderr)
356 raise ValueError(err)
357 if prop_name == 'offset':
359 elif prop_name == 'image_pos':
360 value = entry.image_pos
361 if not self.GetImage()._end_4gb:
364 if prop_name == 'size':
367 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
369 def GetRootSkipAtStart(self):
370 """Get the skip-at-start value for the top-level section
372 This is used to find out the starting offset for root section that
373 contains this section. If this is a top-level section then it returns
374 the skip-at-start offset for this section.
376 This is used to get the absolute position of section within the image.
379 Integer skip-at-start value for the root section containing this
383 return self.section.GetRootSkipAtStart()
384 return self._skip_at_start
386 def GetStartOffset(self):
387 """Get the start offset for this section
390 The first available offset in this section (typically 0)
392 return self._skip_at_start
394 def GetImageSize(self):
395 """Get the size of the image containing this section
398 Image size as an integer number of bytes, which may be None if the
399 image size is dynamic and its sections have not yet been packed
401 return self.GetImage().size
403 def FindEntryType(self, etype):
404 """Find an entry type in the section
407 etype: Entry type to find
409 entry matching that type, or None if not found
411 for entry in self._entries.values():
412 if entry.etype == etype:
416 def GetEntryContents(self):
417 """Call ObtainContents() for each entry in the section
419 todo = self._entries.values()
420 for passnum in range(3):
423 if not entry.ObtainContents():
424 next_todo.append(entry)
429 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
433 def _SetEntryOffsetSize(self, name, offset, size):
434 """Set the offset and size of an entry
437 name: Entry name to update
438 offset: New offset, or None to leave alone
439 size: New size, or None to leave alone
441 entry = self._entries.get(name)
443 self._Raise("Unable to set offset/size for unknown entry '%s'" %
445 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
448 def GetEntryOffsets(self):
449 """Handle entries that want to set the offset/size of other entries
451 This calls each entry's GetOffsets() method. If it returns a list
452 of entries to update, it updates them.
454 for entry in self._entries.values():
455 offset_dict = entry.GetOffsets()
456 for name, info in offset_dict.items():
457 self._SetEntryOffsetSize(name, *info)
461 """Check that the section contents does not exceed its size, etc."""
463 for entry in self._entries.values():
464 contents_size = max(contents_size, entry.offset + entry.size)
466 contents_size -= self._skip_at_start
470 size = self.pad_before + contents_size + self.pad_after
471 size = tools.Align(size, self.align_size)
473 if self.size and contents_size > self.size:
474 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
475 (contents_size, contents_size, self.size, self.size))
478 if self.size != tools.Align(self.size, self.align_size):
479 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
480 (self.size, self.size, self.align_size,
484 def ListEntries(self, entries, indent):
485 """List the files in the section"""
486 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
487 self.image_pos, None, self.offset, self)
488 for entry in self._entries.values():
489 entry.ListEntries(entries, indent + 1)
491 def LoadData(self, decomp=True):
492 for entry in self._entries.values():
493 entry.LoadData(decomp)
494 self.Detail('Loaded data')
497 """Get the image containing this section
499 Note that a top-level section is actually an Image, so this function may
503 Image object containing this section
507 return self.section.GetImage()
510 """Check if the entries in this section will be sorted
513 True if to be sorted, False if entries will be left in the order
514 they appear in the device tree
518 def ReadData(self, decomp=True):
519 tout.Info("ReadData path='%s'" % self.GetPath())
520 parent_data = self.section.ReadData(True)
521 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
522 (self.GetPath(), self.offset, self.offset + self.size,
524 data = parent_data[self.offset:self.offset + self.size]
527 def ReadChildData(self, child, decomp=True):
528 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
529 parent_data = self.ReadData(True)
530 offset = child.offset - self._skip_at_start
531 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
532 (child.GetPath(), child.offset, self._skip_at_start, offset))
533 data = parent_data[offset:offset + child.size]
536 data = tools.Decompress(indata, child.compress)
537 if child.uncomp_size:
538 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
539 (child.GetPath(), len(indata), child.compress,
543 def WriteChildData(self, child):
546 def SetAllowMissing(self, allow_missing):
547 """Set whether a section allows missing external blobs
550 allow_missing: True if allowed, False if not allowed
552 self.allow_missing = allow_missing
553 for entry in self._entries.values():
554 entry.SetAllowMissing(allow_missing)
556 def CheckMissing(self, missing_list):
557 """Check if any entries in this section have missing external blobs
559 If there are missing blobs, the entries are added to the list
562 missing_list: List of Entry objects to be added to
564 for entry in self._entries.values():
565 entry.CheckMissing(missing_list)