]> git.ipfire.org Git - thirdparty/u-boot.git/blob - tools/binman/etype/section.py
Merge tag 'u-boot-imx-20191105' of https://gitlab.denx.de/u-boot/custodians/u-boot-imx
[thirdparty/u-boot.git] / tools / binman / etype / section.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4
5 """Entry-type module for sections (groups of entries)
6
7 Sections are entries which can contain other entries. This allows hierarchical
8 images to be created.
9 """
10
11 from __future__ import print_function
12
13 from collections import OrderedDict
14 import re
15 import sys
16
17 from entry import Entry
18 import fdt_util
19 import tools
20 import tout
21
22
23 class Entry_section(Entry):
24 """Entry that contains other entries
25
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
38
39 Since a section is also an entry, it inherits all the properies of entries
40 too.
41
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.
45 """
46 def __init__(self, section, etype, node, test=False):
47 if not test:
48 Entry.__init__(self, section, etype, node)
49 self._entries = OrderedDict()
50 self._pad_byte = 0
51 self._sort = False
52 self._skip_at_start = None
53 self._end_4gb = False
54
55 def ReadNode(self):
56 """Read properties from the image node"""
57 Entry.ReadNode(self)
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')
62 if self._end_4gb:
63 if not self.size:
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'")
67 else:
68 self._skip_at_start = 0x100000000 - self.size
69 else:
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')
74 if filename:
75 self._filename = filename
76
77 self._ReadEntries()
78
79 def _ReadEntries(self):
80 for node in self._node.subnodes:
81 if node.name == 'hash':
82 continue
83 entry = Entry.Create(self, node)
84 entry.ReadNode()
85 entry.SetPrefix(self._name_prefix)
86 self._entries[node.name] = entry
87
88 def _Raise(self, msg):
89 """Raises an error for this section
90
91 Args:
92 msg: Error message to use in the raise string
93 Raises:
94 ValueError()
95 """
96 raise ValueError("Section '%s': %s" % (self._node.path, msg))
97
98 def GetFdts(self):
99 fdts = {}
100 for entry in self._entries.values():
101 fdts.update(entry.GetFdts())
102 return fdts
103
104 def ProcessFdt(self, fdt):
105 """Allow entries to adjust the device tree
106
107 Some entries need to adjust the device tree for their purposes. This
108 may involve adding or deleting properties.
109 """
110 todo = self._entries.values()
111 for passnum in range(3):
112 next_todo = []
113 for entry in todo:
114 if not entry.ProcessFdt(fdt):
115 next_todo.append(entry)
116 todo = next_todo
117 if not todo:
118 break
119 if todo:
120 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
121 todo)
122 return True
123
124 def ExpandEntries(self):
125 """Expand out any entries which have calculated sub-entries
126
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.
130 """
131 Entry.ExpandEntries(self)
132 for entry in self._entries.values():
133 entry.ExpandEntries()
134
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()
140
141 def ObtainContents(self):
142 return self.GetEntryContents()
143
144 def GetData(self):
145 section_data = b''
146
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)
151 if pad > 0:
152 section_data += tools.GetBytes(self._pad_byte, pad)
153 section_data += data
154 if self.size:
155 pad = self.size - len(section_data)
156 if pad > 0:
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)))
160 return section_data
161
162 def GetOffsets(self):
163 """Handle entries that want to set the offset/size of other entries
164
165 This calls each entry's GetOffsets() method. If it returns a list
166 of entries to update, it updates them.
167 """
168 self.GetEntryOffsets()
169 return {}
170
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():
175 entry.ResetForPack()
176
177 def Pack(self, offset):
178 """Pack all entries into the section"""
179 self._PackEntries()
180 return Entry.Pack(self, offset)
181
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()
188
189 def _ExpandEntries(self):
190 """Expand any entries that are permitted to"""
191 exp_entry = None
192 for entry in self._entries.values():
193 if exp_entry:
194 exp_entry.ExpandToLimit(entry.offset)
195 exp_entry = None
196 if entry.expand_size:
197 exp_entry = entry
198 if exp_entry:
199 exp_entry.ExpandToLimit(self.size)
200
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
207
208 def CheckEntries(self):
209 """Check that entries do not overlap or extend outside the image"""
210 if self._sort:
211 self._SortEntries()
212 self._ExpandEntries()
213 offset = 0
214 prev_name = 'None'
215 for entry in self._entries.values():
216 entry.CheckOffset()
217 if (entry.offset < self._skip_at_start or
218 entry.offset + entry.size > self._skip_at_start +
219 self.size):
220 entry.Raise("Offset %#x (%d) is outside the section starting "
221 "at %#x (%d)" %
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()
230
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)
235
236 def SetCalculatedProperties(self):
237 Entry.SetCalculatedProperties(self)
238 for entry in self._entries.values():
239 entry.SetCalculatedProperties()
240
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)
245
246 def ProcessContents(self):
247 sizes_ok_base = super(Entry_section, self).ProcessContents()
248 sizes_ok = True
249 for entry in self._entries.values():
250 if not entry.ProcessContents():
251 sizes_ok = False
252 return sizes_ok and sizes_ok_base
253
254 def CheckOffset(self):
255 self.CheckEntries()
256
257 def WriteMap(self, fd, indent):
258 """Write a map of the section to a .map file
259
260 Args:
261 fd: File to write the map to
262 """
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)
267
268 def GetEntries(self):
269 return self._entries
270
271 def GetContentsByPhandle(self, phandle, source_entry):
272 """Get the data contents of an entry specified by a phandle
273
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.
276
277 Args:
278 phandle: Phandle to look up (integer)
279 source_entry: Entry containing that phandle (used for error
280 reporting)
281
282 Returns:
283 data from associated entry (as a string), or None if not found
284 """
285 node = self._node.GetFdt().LookupPhandle(phandle)
286 if not node:
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)
292
293 def LookupSymbol(self, sym_name, optional, msg, base_addr):
294 """Look up a symbol in an ELF file
295
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.
298
299 At present the only entry properties supported are:
300 offset
301 image_pos - 'base_addr' is added if this is not an end-at-4gb image
302 size
303
304 Args:
305 sym_name: Symbol name in the ELF file to look up in the format
306 _binman_<entry>_prop_<property> where <entry> is the name of
307 the entry and <property> is the property to find (e.g.
308 _binman_u_boot_prop_offset). As a special case, you can append
309 _any to <entry> to have it search for any matching entry. E.g.
310 _binman_u_boot_any_prop_offset will match entries called u-boot,
311 u-boot-img and u-boot-nodtb)
312 optional: True if the symbol is optional. If False this function
313 will raise if the symbol is not found
314 msg: Message to display if an error occurs
315 base_addr: Base address of image. This is added to the returned
316 image_pos in most cases so that the returned position indicates
317 where the targetted entry/binary has actually been loaded. But
318 if end-at-4gb is used, this is not done, since the binary is
319 already assumed to be linked to the ROM position and using
320 execute-in-place (XIP).
321
322 Returns:
323 Value that should be assigned to that symbol, or None if it was
324 optional and not found
325
326 Raises:
327 ValueError if the symbol is invalid or not found, or references a
328 property which is not supported
329 """
330 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
331 if not m:
332 raise ValueError("%s: Symbol '%s' has invalid format" %
333 (msg, sym_name))
334 entry_name, prop_name = m.groups()
335 entry_name = entry_name.replace('_', '-')
336 entry = self._entries.get(entry_name)
337 if not entry:
338 if entry_name.endswith('-any'):
339 root = entry_name[:-4]
340 for name in self._entries:
341 if name.startswith(root):
342 rest = name[len(root):]
343 if rest in ['', '-img', '-nodtb']:
344 entry = self._entries[name]
345 if not entry:
346 err = ("%s: Entry '%s' not found in list (%s)" %
347 (msg, entry_name, ','.join(self._entries.keys())))
348 if optional:
349 print('Warning: %s' % err, file=sys.stderr)
350 return None
351 raise ValueError(err)
352 if prop_name == 'offset':
353 return entry.offset
354 elif prop_name == 'image_pos':
355 value = entry.image_pos
356 if not self.GetImage()._end_4gb:
357 value += base_addr
358 return value
359 if prop_name == 'size':
360 return entry.size
361 else:
362 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
363
364 def GetRootSkipAtStart(self):
365 """Get the skip-at-start value for the top-level section
366
367 This is used to find out the starting offset for root section that
368 contains this section. If this is a top-level section then it returns
369 the skip-at-start offset for this section.
370
371 This is used to get the absolute position of section within the image.
372
373 Returns:
374 Integer skip-at-start value for the root section containing this
375 section
376 """
377 if self.section:
378 return self.section.GetRootSkipAtStart()
379 return self._skip_at_start
380
381 def GetStartOffset(self):
382 """Get the start offset for this section
383
384 Returns:
385 The first available offset in this section (typically 0)
386 """
387 return self._skip_at_start
388
389 def GetImageSize(self):
390 """Get the size of the image containing this section
391
392 Returns:
393 Image size as an integer number of bytes, which may be None if the
394 image size is dynamic and its sections have not yet been packed
395 """
396 return self.GetImage().size
397
398 def FindEntryType(self, etype):
399 """Find an entry type in the section
400
401 Args:
402 etype: Entry type to find
403 Returns:
404 entry matching that type, or None if not found
405 """
406 for entry in self._entries.values():
407 if entry.etype == etype:
408 return entry
409 return None
410
411 def GetEntryContents(self):
412 """Call ObtainContents() for the section
413 """
414 todo = self._entries.values()
415 for passnum in range(3):
416 next_todo = []
417 for entry in todo:
418 if not entry.ObtainContents():
419 next_todo.append(entry)
420 todo = next_todo
421 if not todo:
422 break
423 if todo:
424 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
425 todo)
426 return True
427
428 def _SetEntryOffsetSize(self, name, offset, size):
429 """Set the offset and size of an entry
430
431 Args:
432 name: Entry name to update
433 offset: New offset, or None to leave alone
434 size: New size, or None to leave alone
435 """
436 entry = self._entries.get(name)
437 if not entry:
438 self._Raise("Unable to set offset/size for unknown entry '%s'" %
439 name)
440 entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
441 size)
442
443 def GetEntryOffsets(self):
444 """Handle entries that want to set the offset/size of other entries
445
446 This calls each entry's GetOffsets() method. If it returns a list
447 of entries to update, it updates them.
448 """
449 for entry in self._entries.values():
450 offset_dict = entry.GetOffsets()
451 for name, info in offset_dict.items():
452 self._SetEntryOffsetSize(name, *info)
453
454
455 def CheckSize(self):
456 """Check that the image contents does not exceed its size, etc."""
457 contents_size = 0
458 for entry in self._entries.values():
459 contents_size = max(contents_size, entry.offset + entry.size)
460
461 contents_size -= self._skip_at_start
462
463 size = self.size
464 if not size:
465 size = self.pad_before + contents_size + self.pad_after
466 size = tools.Align(size, self.align_size)
467
468 if self.size and contents_size > self.size:
469 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
470 (contents_size, contents_size, self.size, self.size))
471 if not self.size:
472 self.size = size
473 if self.size != tools.Align(self.size, self.align_size):
474 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
475 (self.size, self.size, self.align_size,
476 self.align_size))
477 return size
478
479 def ListEntries(self, entries, indent):
480 """List the files in the section"""
481 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
482 self.image_pos, None, self.offset, self)
483 for entry in self._entries.values():
484 entry.ListEntries(entries, indent + 1)
485
486 def LoadData(self, decomp=True):
487 for entry in self._entries.values():
488 entry.LoadData(decomp)
489 self.Detail('Loaded data')
490
491 def GetImage(self):
492 """Get the image containing this section
493
494 Note that a top-level section is actually an Image, so this function may
495 return self.
496
497 Returns:
498 Image object containing this section
499 """
500 if not self.section:
501 return self
502 return self.section.GetImage()
503
504 def GetSort(self):
505 """Check if the entries in this section will be sorted
506
507 Returns:
508 True if to be sorted, False if entries will be left in the order
509 they appear in the device tree
510 """
511 return self._sort
512
513 def ReadData(self, decomp=True):
514 tout.Info("ReadData path='%s'" % self.GetPath())
515 parent_data = self.section.ReadData(True)
516 tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
517 (self.GetPath(), self.offset, self.offset + self.size,
518 self.size))
519 data = parent_data[self.offset:self.offset + self.size]
520 return data
521
522 def ReadChildData(self, child, decomp=True):
523 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
524 parent_data = self.ReadData(True)
525 offset = child.offset - self._skip_at_start
526 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
527 (child.GetPath(), child.offset, self._skip_at_start, offset))
528 data = parent_data[offset:offset + child.size]
529 if decomp:
530 indata = data
531 data = tools.Decompress(indata, child.compress)
532 if child.uncomp_size:
533 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
534 (child.GetPath(), len(indata), child.compress,
535 len(data)))
536 return data
537
538 def WriteChildData(self, child):
539 return True