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
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
38 Since a section is also an entry, it inherits all the properies of entries
41 A section is an entry which can contain other entries, thus allowing
42 hierarchical images to be created. See 'Sections and hierarchical images'
43 in the binman README for more information.
45 def __init__(self, section, etype, node, test=False):
47 Entry.__init__(self, section, etype, node)
48 self._entries = OrderedDict()
51 self._skip_at_start = None
55 """Read properties from the image node"""
57 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
58 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
59 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
60 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
63 self.Raise("Section size must be provided when using end-at-4gb")
64 if self._skip_at_start is not None:
65 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
67 self._skip_at_start = 0x100000000 - self.size
69 if self._skip_at_start is None:
70 self._skip_at_start = 0
71 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
72 filename = fdt_util.GetString(self._node, 'filename')
74 self._filename = filename
78 def _ReadEntries(self):
79 for node in self._node.subnodes:
80 if node.name == 'hash':
82 entry = Entry.Create(self, node)
84 entry.SetPrefix(self._name_prefix)
85 self._entries[node.name] = entry
87 def _Raise(self, msg):
88 """Raises an error for this section
91 msg: Error message to use in the raise string
95 raise ValueError("Section '%s': %s" % (self._node.path, msg))
99 for entry in self._entries.values():
100 fdts.update(entry.GetFdts())
103 def ProcessFdt(self, fdt):
104 """Allow entries to adjust the device tree
106 Some entries need to adjust the device tree for their purposes. This
107 may involve adding or deleting properties.
109 todo = self._entries.values()
110 for passnum in range(3):
113 if not entry.ProcessFdt(fdt):
114 next_todo.append(entry)
119 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
123 def ExpandEntries(self):
124 """Expand out any entries which have calculated sub-entries
126 Some entries are expanded out at runtime, e.g. 'files', which produces
127 a section containing a list of files. Process these entries so that
128 this information is added to the device tree.
130 Entry.ExpandEntries(self)
131 for entry in self._entries.values():
132 entry.ExpandEntries()
134 def AddMissingProperties(self):
135 """Add new properties to the device tree as needed for this entry"""
136 Entry.AddMissingProperties(self)
137 for entry in self._entries.values():
138 entry.AddMissingProperties()
140 def ObtainContents(self):
141 return self.GetEntryContents()
144 section_data = tools.GetBytes(self._pad_byte, self.size)
146 for entry in self._entries.values():
147 data = entry.GetData()
148 base = self.pad_before + entry.offset - self._skip_at_start
149 section_data = (section_data[:base] + data +
150 section_data[base + len(data):])
151 self.Detail('GetData: %d entries, total size %#x' %
152 (len(self._entries), len(section_data)))
155 def GetOffsets(self):
156 """Handle entries that want to set the offset/size of other entries
158 This calls each entry's GetOffsets() method. If it returns a list
159 of entries to update, it updates them.
161 self.GetEntryOffsets()
164 def ResetForPack(self):
165 """Reset offset/size fields so that packing can be done again"""
166 Entry.ResetForPack(self)
167 for entry in self._entries.values():
170 def Pack(self, offset):
171 """Pack all entries into the section"""
173 return Entry.Pack(self, offset)
175 def _PackEntries(self):
176 """Pack all entries into the image"""
177 offset = self._skip_at_start
178 for entry in self._entries.values():
179 offset = entry.Pack(offset)
180 self.size = self.CheckSize()
182 def _ExpandEntries(self):
183 """Expand any entries that are permitted to"""
185 for entry in self._entries.values():
187 exp_entry.ExpandToLimit(entry.offset)
189 if entry.expand_size:
192 exp_entry.ExpandToLimit(self.size)
194 def _SortEntries(self):
195 """Sort entries by offset"""
196 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
197 self._entries.clear()
198 for entry in entries:
199 self._entries[entry._node.name] = entry
201 def CheckEntries(self):
202 """Check that entries do not overlap or extend outside the image"""
205 self._ExpandEntries()
208 for entry in self._entries.values():
210 if (entry.offset < self._skip_at_start or
211 entry.offset + entry.size > self._skip_at_start +
213 entry.Raise("Offset %#x (%d) is outside the section starting "
215 (entry.offset, entry.offset, self._skip_at_start,
216 self._skip_at_start))
217 if entry.offset < offset:
218 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
219 "ending at %#x (%d)" %
220 (entry.offset, entry.offset, prev_name, offset, offset))
221 offset = entry.offset + entry.size
222 prev_name = entry.GetPath()
224 def WriteSymbols(self, section):
225 """Write symbol values into binary files for access at run time"""
226 for entry in self._entries.values():
227 entry.WriteSymbols(self)
229 def SetCalculatedProperties(self):
230 Entry.SetCalculatedProperties(self)
231 for entry in self._entries.values():
232 entry.SetCalculatedProperties()
234 def SetImagePos(self, image_pos):
235 Entry.SetImagePos(self, image_pos)
236 for entry in self._entries.values():
237 entry.SetImagePos(image_pos + self.offset)
239 def ProcessContents(self):
240 sizes_ok_base = super(Entry_section, self).ProcessContents()
242 for entry in self._entries.values():
243 if not entry.ProcessContents():
245 return sizes_ok and sizes_ok_base
247 def CheckOffset(self):
250 def WriteMap(self, fd, indent):
251 """Write a map of the section to a .map file
254 fd: File to write the map to
256 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
257 self.size, self.image_pos)
258 for entry in self._entries.values():
259 entry.WriteMap(fd, indent + 1)
261 def GetEntries(self):
264 def GetContentsByPhandle(self, phandle, source_entry):
265 """Get the data contents of an entry specified by a phandle
267 This uses a phandle to look up a node and and find the entry
268 associated with it. Then it returnst he contents of that entry.
271 phandle: Phandle to look up (integer)
272 source_entry: Entry containing that phandle (used for error
276 data from associated entry (as a string), or None if not found
278 node = self._node.GetFdt().LookupPhandle(phandle)
280 source_entry.Raise("Cannot find node for phandle %d" % phandle)
281 for entry in self._entries.values():
282 if entry._node == node:
283 return entry.GetData()
284 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
286 def LookupSymbol(self, sym_name, optional, msg):
287 """Look up a symbol in an ELF file
289 Looks up a symbol in an ELF file. Only entry types which come from an
290 ELF image can be used by this function.
292 At present the only entry property supported is offset.
295 sym_name: Symbol name in the ELF file to look up in the format
296 _binman_<entry>_prop_<property> where <entry> is the name of
297 the entry and <property> is the property to find (e.g.
298 _binman_u_boot_prop_offset). As a special case, you can append
299 _any to <entry> to have it search for any matching entry. E.g.
300 _binman_u_boot_any_prop_offset will match entries called u-boot,
301 u-boot-img and u-boot-nodtb)
302 optional: True if the symbol is optional. If False this function
303 will raise if the symbol is not found
304 msg: Message to display if an error occurs
307 Value that should be assigned to that symbol, or None if it was
308 optional and not found
311 ValueError if the symbol is invalid or not found, or references a
312 property which is not supported
314 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
316 raise ValueError("%s: Symbol '%s' has invalid format" %
318 entry_name, prop_name = m.groups()
319 entry_name = entry_name.replace('_', '-')
320 entry = self._entries.get(entry_name)
322 if entry_name.endswith('-any'):
323 root = entry_name[:-4]
324 for name in self._entries:
325 if name.startswith(root):
326 rest = name[len(root):]
327 if rest in ['', '-img', '-nodtb']:
328 entry = self._entries[name]
330 err = ("%s: Entry '%s' not found in list (%s)" %
331 (msg, entry_name, ','.join(self._entries.keys())))
333 print('Warning: %s' % err, file=sys.stderr)
335 raise ValueError(err)
336 if prop_name == 'offset':
338 elif prop_name == 'image_pos':
339 return entry.image_pos
341 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
343 def GetRootSkipAtStart(self):
344 """Get the skip-at-start value for the top-level section
346 This is used to find out the starting offset for root section that
347 contains this section. If this is a top-level section then it returns
348 the skip-at-start offset for this section.
350 This is used to get the absolute position of section within the image.
353 Integer skip-at-start value for the root section containing this
357 return self.section.GetRootSkipAtStart()
358 return self._skip_at_start
360 def GetStartOffset(self):
361 """Get the start offset for this section
364 The first available offset in this section (typically 0)
366 return self._skip_at_start
368 def GetImageSize(self):
369 """Get the size of the image containing this section
372 Image size as an integer number of bytes, which may be None if the
373 image size is dynamic and its sections have not yet been packed
375 return self.GetImage().size
377 def FindEntryType(self, etype):
378 """Find an entry type in the section
381 etype: Entry type to find
383 entry matching that type, or None if not found
385 for entry in self._entries.values():
386 if entry.etype == etype:
390 def GetEntryContents(self):
391 """Call ObtainContents() for the section
393 todo = self._entries.values()
394 for passnum in range(3):
397 if not entry.ObtainContents():
398 next_todo.append(entry)
403 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
407 def _SetEntryOffsetSize(self, name, offset, size):
408 """Set the offset and size of an entry
411 name: Entry name to update
412 offset: New offset, or None to leave alone
413 size: New size, or None to leave alone
415 entry = self._entries.get(name)
417 self._Raise("Unable to set offset/size for unknown entry '%s'" %
419 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
422 def GetEntryOffsets(self):
423 """Handle entries that want to set the offset/size of other entries
425 This calls each entry's GetOffsets() method. If it returns a list
426 of entries to update, it updates them.
428 for entry in self._entries.values():
429 offset_dict = entry.GetOffsets()
430 for name, info in offset_dict.items():
431 self._SetEntryOffsetSize(name, *info)
435 """Check that the image contents does not exceed its size, etc."""
437 for entry in self._entries.values():
438 contents_size = max(contents_size, entry.offset + entry.size)
440 contents_size -= self._skip_at_start
444 size = self.pad_before + contents_size + self.pad_after
445 size = tools.Align(size, self.align_size)
447 if self.size and contents_size > self.size:
448 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
449 (contents_size, contents_size, self.size, self.size))
452 if self.size != tools.Align(self.size, self.align_size):
453 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
454 (self.size, self.size, self.align_size,
458 def ListEntries(self, entries, indent):
459 """List the files in the section"""
460 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
461 self.image_pos, None, self.offset, self)
462 for entry in self._entries.values():
463 entry.ListEntries(entries, indent + 1)
465 def LoadData(self, decomp=True):
466 for entry in self._entries.values():
467 entry.LoadData(decomp)
468 self.Detail('Loaded data')
471 """Get the image containing this section
473 Note that a top-level section is actually an Image, so this function may
477 Image object containing this section
481 return self.section.GetImage()
484 """Check if the entries in this section will be sorted
487 True if to be sorted, False if entries will be left in the order
488 they appear in the device tree