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)
49 self.image = section.image
50 self._entries = OrderedDict()
53 self._skip_at_start = None
59 def _Raise(self, msg):
60 """Raises an error for this section
63 msg: Error message to use in the raise string
67 raise ValueError("Section '%s': %s" % (self._node.path, msg))
70 """Read properties from the image node"""
71 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
72 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
73 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
74 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
77 self.Raise("Section size must be provided when using end-at-4gb")
78 if self._skip_at_start is not None:
79 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
81 self._skip_at_start = 0x100000000 - self.size
83 if self._skip_at_start is None:
84 self._skip_at_start = 0
85 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
86 filename = fdt_util.GetString(self._node, 'filename')
88 self._filename = filename
90 def _ReadEntries(self):
91 for node in self._node.subnodes:
92 if node.name == 'hash':
94 entry = Entry.Create(self, node)
95 entry.SetPrefix(self._name_prefix)
96 self._entries[node.name] = entry
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()
145 section_data = tools.GetBytes(self._pad_byte, self.size)
147 for entry in self._entries.values():
148 data = entry.GetData()
149 base = self.pad_before + entry.offset - self._skip_at_start
150 section_data = (section_data[:base] + data +
151 section_data[base + len(data):])
152 self.Detail('GetData: %d entries, total size %#x' %
153 (len(self._entries), len(section_data)))
156 def GetOffsets(self):
157 """Handle entries that want to set the offset/size of other entries
159 This calls each entry's GetOffsets() method. If it returns a list
160 of entries to update, it updates them.
162 self.GetEntryOffsets()
165 def ResetForPack(self):
166 """Reset offset/size fields so that packing can be done again"""
167 Entry.ResetForPack(self)
168 for entry in self._entries.values():
171 def Pack(self, offset):
172 """Pack all entries into the section"""
174 return Entry.Pack(self, offset)
176 def _PackEntries(self):
177 """Pack all entries into the image"""
178 offset = self._skip_at_start
179 for entry in self._entries.values():
180 offset = entry.Pack(offset)
181 self.size = self.CheckSize()
183 def _ExpandEntries(self):
184 """Expand any entries that are permitted to"""
186 for entry in self._entries.values():
188 exp_entry.ExpandToLimit(entry.offset)
190 if entry.expand_size:
193 exp_entry.ExpandToLimit(self.size)
195 def _SortEntries(self):
196 """Sort entries by offset"""
197 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
198 self._entries.clear()
199 for entry in entries:
200 self._entries[entry._node.name] = entry
202 def CheckEntries(self):
203 """Check that entries do not overlap or extend outside the image"""
206 self._ExpandEntries()
209 for entry in self._entries.values():
211 if (entry.offset < self._skip_at_start or
212 entry.offset + entry.size > self._skip_at_start +
214 entry.Raise("Offset %#x (%d) is outside the section starting "
216 (entry.offset, entry.offset, self._skip_at_start,
217 self._skip_at_start))
218 if entry.offset < offset:
219 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
220 "ending at %#x (%d)" %
221 (entry.offset, entry.offset, prev_name, offset, offset))
222 offset = entry.offset + entry.size
223 prev_name = entry.GetPath()
225 def WriteSymbols(self, section):
226 """Write symbol values into binary files for access at run time"""
227 for entry in self._entries.values():
228 entry.WriteSymbols(self)
230 def SetCalculatedProperties(self):
231 Entry.SetCalculatedProperties(self)
232 for entry in self._entries.values():
233 entry.SetCalculatedProperties()
235 def SetImagePos(self, image_pos):
236 Entry.SetImagePos(self, image_pos)
237 for entry in self._entries.values():
238 entry.SetImagePos(image_pos + self.offset)
240 def ProcessContents(self):
241 sizes_ok_base = super(Entry_section, self).ProcessContents()
243 for entry in self._entries.values():
244 if not entry.ProcessContents():
246 return sizes_ok and sizes_ok_base
248 def CheckOffset(self):
251 def WriteMap(self, fd, indent):
252 """Write a map of the section to a .map file
255 fd: File to write the map to
257 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
258 self.size, self.image_pos)
259 for entry in self._entries.values():
260 entry.WriteMap(fd, indent + 1)
262 def GetEntries(self):
265 def GetContentsByPhandle(self, phandle, source_entry):
266 """Get the data contents of an entry specified by a phandle
268 This uses a phandle to look up a node and and find the entry
269 associated with it. Then it returnst he contents of that entry.
272 phandle: Phandle to look up (integer)
273 source_entry: Entry containing that phandle (used for error
277 data from associated entry (as a string), or None if not found
279 node = self._node.GetFdt().LookupPhandle(phandle)
281 source_entry.Raise("Cannot find node for phandle %d" % phandle)
282 for entry in self._entries.values():
283 if entry._node == node:
284 return entry.GetData()
285 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
287 def LookupSymbol(self, sym_name, optional, msg):
288 """Look up a symbol in an ELF file
290 Looks up a symbol in an ELF file. Only entry types which come from an
291 ELF image can be used by this function.
293 At present the only entry property supported is offset.
296 sym_name: Symbol name in the ELF file to look up in the format
297 _binman_<entry>_prop_<property> where <entry> is the name of
298 the entry and <property> is the property to find (e.g.
299 _binman_u_boot_prop_offset). As a special case, you can append
300 _any to <entry> to have it search for any matching entry. E.g.
301 _binman_u_boot_any_prop_offset will match entries called u-boot,
302 u-boot-img and u-boot-nodtb)
303 optional: True if the symbol is optional. If False this function
304 will raise if the symbol is not found
305 msg: Message to display if an error occurs
308 Value that should be assigned to that symbol, or None if it was
309 optional and not found
312 ValueError if the symbol is invalid or not found, or references a
313 property which is not supported
315 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
317 raise ValueError("%s: Symbol '%s' has invalid format" %
319 entry_name, prop_name = m.groups()
320 entry_name = entry_name.replace('_', '-')
321 entry = self._entries.get(entry_name)
323 if entry_name.endswith('-any'):
324 root = entry_name[:-4]
325 for name in self._entries:
326 if name.startswith(root):
327 rest = name[len(root):]
328 if rest in ['', '-img', '-nodtb']:
329 entry = self._entries[name]
331 err = ("%s: Entry '%s' not found in list (%s)" %
332 (msg, entry_name, ','.join(self._entries.keys())))
334 print('Warning: %s' % err, file=sys.stderr)
336 raise ValueError(err)
337 if prop_name == 'offset':
339 elif prop_name == 'image_pos':
340 return entry.image_pos
342 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
344 def GetRootSkipAtStart(self):
345 """Get the skip-at-start value for the top-level section
347 This is used to find out the starting offset for root section that
348 contains this section. If this is a top-level section then it returns
349 the skip-at-start offset for this section.
351 This is used to get the absolute position of section within the image.
354 Integer skip-at-start value for the root section containing this
358 return self.section.GetRootSkipAtStart()
359 return self._skip_at_start
361 def GetStartOffset(self):
362 """Get the start offset for this section
365 The first available offset in this section (typically 0)
367 return self._skip_at_start
369 def GetImageSize(self):
370 """Get the size of the image containing this section
373 Image size as an integer number of bytes, which may be None if the
374 image size is dynamic and its sections have not yet been packed
376 return self.image.size
378 def FindEntryType(self, etype):
379 """Find an entry type in the section
382 etype: Entry type to find
384 entry matching that type, or None if not found
386 for entry in self._entries.values():
387 if entry.etype == etype:
391 def GetEntryContents(self):
392 """Call ObtainContents() for the section
394 todo = self._entries.values()
395 for passnum in range(3):
398 if not entry.ObtainContents():
399 next_todo.append(entry)
404 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
408 def _SetEntryOffsetSize(self, name, offset, size):
409 """Set the offset and size of an entry
412 name: Entry name to update
413 offset: New offset, or None to leave alone
414 size: New size, or None to leave alone
416 entry = self._entries.get(name)
418 self._Raise("Unable to set offset/size for unknown entry '%s'" %
420 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
423 def GetEntryOffsets(self):
424 """Handle entries that want to set the offset/size of other entries
426 This calls each entry's GetOffsets() method. If it returns a list
427 of entries to update, it updates them.
429 for entry in self._entries.values():
430 offset_dict = entry.GetOffsets()
431 for name, info in offset_dict.items():
432 self._SetEntryOffsetSize(name, *info)
436 """Check that the image contents does not exceed its size, etc."""
438 for entry in self._entries.values():
439 contents_size = max(contents_size, entry.offset + entry.size)
441 contents_size -= self._skip_at_start
445 size = self.pad_before + contents_size + self.pad_after
446 size = tools.Align(size, self.align_size)
448 if self.size and contents_size > self.size:
449 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
450 (contents_size, contents_size, self.size, self.size))
453 if self.size != tools.Align(self.size, self.align_size):
454 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
455 (self.size, self.size, self.align_size,
459 def ListEntries(self, entries, indent):
460 """List the files in the section"""
461 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
462 self.image_pos, None, self.offset, self)
463 for entry in self._entries.values():
464 entry.ListEntries(entries, indent + 1)