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 __future__ import print_function
13 from collections import OrderedDict
17 from entry import Entry
23 class Entry_section(Entry):
24 """Entry that contains other entries
26 Properties / Entry arguments: (see binman README for more information)
27 pad-byte: Pad byte to use when padding
28 sort-by-offset: True if entries should be sorted by offset, False if
29 they must be in-order in the device tree description
30 end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
31 skip-at-start: Number of bytes before the first entry starts. These
32 effectively adjust the starting offset of entries. For example,
33 if this is 16, then the first entry would start at 16. An entry
34 with offset = 20 would in fact be written at offset 4 in the image
35 file, since the first 16 bytes are skipped when writing.
36 name-prefix: Adds a prefix to the name of every entry in the section
37 when writing out the map
39 Since a section is also an entry, it inherits all the properies of entries
42 A section is an entry which can contain other entries, thus allowing
43 hierarchical images to be created. See 'Sections and hierarchical images'
44 in the binman README for more information.
46 def __init__(self, section, etype, node, test=False):
48 Entry.__init__(self, section, etype, node)
49 self._entries = OrderedDict()
52 self._skip_at_start = None
56 """Read properties from the image node"""
58 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
59 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
60 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
61 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
64 self.Raise("Section size must be provided when using end-at-4gb")
65 if self._skip_at_start is not None:
66 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
68 self._skip_at_start = 0x100000000 - self.size
70 if self._skip_at_start is None:
71 self._skip_at_start = 0
72 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
73 filename = fdt_util.GetString(self._node, 'filename')
75 self._filename = filename
79 def _ReadEntries(self):
80 for node in self._node.subnodes:
81 if node.name == 'hash':
83 entry = Entry.Create(self, node)
85 entry.SetPrefix(self._name_prefix)
86 self._entries[node.name] = entry
88 def _Raise(self, msg):
89 """Raises an error for this section
92 msg: Error message to use in the raise string
96 raise ValueError("Section '%s': %s" % (self._node.path, msg))
100 for entry in self._entries.values():
101 fdts.update(entry.GetFdts())
104 def ProcessFdt(self, fdt):
105 """Allow entries to adjust the device tree
107 Some entries need to adjust the device tree for their purposes. This
108 may involve adding or deleting properties.
110 todo = self._entries.values()
111 for passnum in range(3):
114 if not entry.ProcessFdt(fdt):
115 next_todo.append(entry)
120 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
124 def ExpandEntries(self):
125 """Expand out any entries which have calculated sub-entries
127 Some entries are expanded out at runtime, e.g. 'files', which produces
128 a section containing a list of files. Process these entries so that
129 this information is added to the device tree.
131 Entry.ExpandEntries(self)
132 for entry in self._entries.values():
133 entry.ExpandEntries()
135 def AddMissingProperties(self):
136 """Add new properties to the device tree as needed for this entry"""
137 Entry.AddMissingProperties(self)
138 for entry in self._entries.values():
139 entry.AddMissingProperties()
141 def ObtainContents(self):
142 return self.GetEntryContents()
147 for entry in self._entries.values():
148 data = entry.GetData()
149 base = self.pad_before + (entry.offset or 0) - self._skip_at_start
150 pad = base - len(section_data)
152 section_data += tools.GetBytes(self._pad_byte, pad)
155 pad = self.size - len(section_data)
157 section_data += tools.GetBytes(self._pad_byte, pad)
158 self.Detail('GetData: %d entries, total size %#x' %
159 (len(self._entries), len(section_data)))
162 def GetOffsets(self):
163 """Handle entries that want to set the offset/size of other entries
165 This calls each entry's GetOffsets() method. If it returns a list
166 of entries to update, it updates them.
168 self.GetEntryOffsets()
171 def ResetForPack(self):
172 """Reset offset/size fields so that packing can be done again"""
173 Entry.ResetForPack(self)
174 for entry in self._entries.values():
177 def Pack(self, offset):
178 """Pack all entries into the section"""
180 return Entry.Pack(self, offset)
182 def _PackEntries(self):
183 """Pack all entries into the image"""
184 offset = self._skip_at_start
185 for entry in self._entries.values():
186 offset = entry.Pack(offset)
187 self.size = self.CheckSize()
189 def _ExpandEntries(self):
190 """Expand any entries that are permitted to"""
192 for entry in self._entries.values():
194 exp_entry.ExpandToLimit(entry.offset)
196 if entry.expand_size:
199 exp_entry.ExpandToLimit(self.size)
201 def _SortEntries(self):
202 """Sort entries by offset"""
203 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
204 self._entries.clear()
205 for entry in entries:
206 self._entries[entry._node.name] = entry
208 def CheckEntries(self):
209 """Check that entries do not overlap or extend outside the image"""
212 self._ExpandEntries()
215 for entry in self._entries.values():
217 if (entry.offset < self._skip_at_start or
218 entry.offset + entry.size > self._skip_at_start +
220 entry.Raise("Offset %#x (%d) is outside the section starting "
222 (entry.offset, entry.offset, self._skip_at_start,
223 self._skip_at_start))
224 if entry.offset < offset:
225 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
226 "ending at %#x (%d)" %
227 (entry.offset, entry.offset, prev_name, offset, offset))
228 offset = entry.offset + entry.size
229 prev_name = entry.GetPath()
231 def WriteSymbols(self, section):
232 """Write symbol values into binary files for access at run time"""
233 for entry in self._entries.values():
234 entry.WriteSymbols(self)
236 def SetCalculatedProperties(self):
237 Entry.SetCalculatedProperties(self)
238 for entry in self._entries.values():
239 entry.SetCalculatedProperties()
241 def SetImagePos(self, image_pos):
242 Entry.SetImagePos(self, image_pos)
243 for entry in self._entries.values():
244 entry.SetImagePos(image_pos + self.offset)
246 def ProcessContents(self):
247 sizes_ok_base = super(Entry_section, self).ProcessContents()
249 for entry in self._entries.values():
250 if not entry.ProcessContents():
252 return sizes_ok and sizes_ok_base
254 def CheckOffset(self):
257 def WriteMap(self, fd, indent):
258 """Write a map of the section to a .map file
261 fd: File to write the map to
263 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
264 self.size, self.image_pos)
265 for entry in self._entries.values():
266 entry.WriteMap(fd, indent + 1)
268 def GetEntries(self):
271 def GetContentsByPhandle(self, phandle, source_entry):
272 """Get the data contents of an entry specified by a phandle
274 This uses a phandle to look up a node and and find the entry
275 associated with it. Then it returnst he contents of that entry.
278 phandle: Phandle to look up (integer)
279 source_entry: Entry containing that phandle (used for error
283 data from associated entry (as a string), or None if not found
285 node = self._node.GetFdt().LookupPhandle(phandle)
287 source_entry.Raise("Cannot find node for phandle %d" % phandle)
288 for entry in self._entries.values():
289 if entry._node == node:
290 return entry.GetData()
291 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
293 def LookupSymbol(self, sym_name, optional, msg):
294 """Look up a symbol in an ELF file
296 Looks up a symbol in an ELF file. Only entry types which come from an
297 ELF image can be used by this function.
299 At present the only entry property supported is offset.
302 sym_name: Symbol name in the ELF file to look up in the format
303 _binman_<entry>_prop_<property> where <entry> is the name of
304 the entry and <property> is the property to find (e.g.
305 _binman_u_boot_prop_offset). As a special case, you can append
306 _any to <entry> to have it search for any matching entry. E.g.
307 _binman_u_boot_any_prop_offset will match entries called u-boot,
308 u-boot-img and u-boot-nodtb)
309 optional: True if the symbol is optional. If False this function
310 will raise if the symbol is not found
311 msg: Message to display if an error occurs
314 Value that should be assigned to that symbol, or None if it was
315 optional and not found
318 ValueError if the symbol is invalid or not found, or references a
319 property which is not supported
321 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
323 raise ValueError("%s: Symbol '%s' has invalid format" %
325 entry_name, prop_name = m.groups()
326 entry_name = entry_name.replace('_', '-')
327 entry = self._entries.get(entry_name)
329 if entry_name.endswith('-any'):
330 root = entry_name[:-4]
331 for name in self._entries:
332 if name.startswith(root):
333 rest = name[len(root):]
334 if rest in ['', '-img', '-nodtb']:
335 entry = self._entries[name]
337 err = ("%s: Entry '%s' not found in list (%s)" %
338 (msg, entry_name, ','.join(self._entries.keys())))
340 print('Warning: %s' % err, file=sys.stderr)
342 raise ValueError(err)
343 if prop_name == 'offset':
345 elif prop_name == 'image_pos':
346 return entry.image_pos
348 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
350 def GetRootSkipAtStart(self):
351 """Get the skip-at-start value for the top-level section
353 This is used to find out the starting offset for root section that
354 contains this section. If this is a top-level section then it returns
355 the skip-at-start offset for this section.
357 This is used to get the absolute position of section within the image.
360 Integer skip-at-start value for the root section containing this
364 return self.section.GetRootSkipAtStart()
365 return self._skip_at_start
367 def GetStartOffset(self):
368 """Get the start offset for this section
371 The first available offset in this section (typically 0)
373 return self._skip_at_start
375 def GetImageSize(self):
376 """Get the size of the image containing this section
379 Image size as an integer number of bytes, which may be None if the
380 image size is dynamic and its sections have not yet been packed
382 return self.GetImage().size
384 def FindEntryType(self, etype):
385 """Find an entry type in the section
388 etype: Entry type to find
390 entry matching that type, or None if not found
392 for entry in self._entries.values():
393 if entry.etype == etype:
397 def GetEntryContents(self):
398 """Call ObtainContents() for the section
400 todo = self._entries.values()
401 for passnum in range(3):
404 if not entry.ObtainContents():
405 next_todo.append(entry)
410 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
414 def _SetEntryOffsetSize(self, name, offset, size):
415 """Set the offset and size of an entry
418 name: Entry name to update
419 offset: New offset, or None to leave alone
420 size: New size, or None to leave alone
422 entry = self._entries.get(name)
424 self._Raise("Unable to set offset/size for unknown entry '%s'" %
426 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
429 def GetEntryOffsets(self):
430 """Handle entries that want to set the offset/size of other entries
432 This calls each entry's GetOffsets() method. If it returns a list
433 of entries to update, it updates them.
435 for entry in self._entries.values():
436 offset_dict = entry.GetOffsets()
437 for name, info in offset_dict.items():
438 self._SetEntryOffsetSize(name, *info)
442 """Check that the image contents does not exceed its size, etc."""
444 for entry in self._entries.values():
445 contents_size = max(contents_size, entry.offset + entry.size)
447 contents_size -= self._skip_at_start
451 size = self.pad_before + contents_size + self.pad_after
452 size = tools.Align(size, self.align_size)
454 if self.size and contents_size > self.size:
455 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
456 (contents_size, contents_size, self.size, self.size))
459 if self.size != tools.Align(self.size, self.align_size):
460 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
461 (self.size, self.size, self.align_size,
465 def ListEntries(self, entries, indent):
466 """List the files in the section"""
467 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
468 self.image_pos, None, self.offset, self)
469 for entry in self._entries.values():
470 entry.ListEntries(entries, indent + 1)
472 def LoadData(self, decomp=True):
473 for entry in self._entries.values():
474 entry.LoadData(decomp)
475 self.Detail('Loaded data')
478 """Get the image containing this section
480 Note that a top-level section is actually an Image, so this function may
484 Image object containing this section
488 return self.section.GetImage()
491 """Check if the entries in this section will be sorted
494 True if to be sorted, False if entries will be left in the order
495 they appear in the device tree
499 def ReadData(self, decomp=True):
500 tout.Info("ReadData path='%s'" % self.GetPath())
501 parent_data = self.section.ReadData(True)
502 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
503 (self.GetPath(), self.offset, self.offset + self.size,
505 data = parent_data[self.offset:self.offset + self.size]
508 def ReadChildData(self, child, decomp=True):
509 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
510 parent_data = self.ReadData(True)
511 offset = child.offset - self._skip_at_start
512 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
513 (child.GetPath(), child.offset, self._skip_at_start, offset))
514 data = parent_data[offset:offset + child.size]
517 data = tools.Decompress(indata, child.compress)
518 if child.uncomp_size:
519 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
520 (child.GetPath(), len(indata), child.compress,
524 def WriteChildData(self, child):