]> git.ipfire.org Git - thirdparty/u-boot.git/blob - tools/binman/etype/section.py
Merge tag 'dm-pull-28mar21' of git://git.denx.de/u-boot-dm into next
[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 collections import OrderedDict
12 import re
13 import sys
14
15 from binman.entry import Entry
16 from dtoc import fdt_util
17 from patman import tools
18 from patman import tout
19 from patman.tools import ToHexSize
20
21
22 class Entry_section(Entry):
23 """Entry that contains other entries
24
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
30 end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
31
32 skip-at-start: Number of bytes before the first entry starts. These
33 effectively adjust the starting offset of entries. For example,
34 if this is 16, then the first entry would start at 16. An entry
35 with offset = 20 would in fact be written at offset 4 in the image
36 file, since the first 16 bytes are skipped when writing.
37 name-prefix: Adds a prefix to the name of every entry in the section
38 when writing out the map
39 align_default: Default alignment for this section, if no alignment is
40 given in the entry
41
42 Properties:
43 allow_missing: True if this section permits external blobs to be
44 missing their contents. The second will produce an image but of
45 course it will not work.
46
47 Since a section is also an entry, it inherits all the properies of entries
48 too.
49
50 A section is an entry which can contain other entries, thus allowing
51 hierarchical images to be created. See 'Sections and hierarchical images'
52 in the binman README for more information.
53 """
54 def __init__(self, section, etype, node, test=False):
55 if not test:
56 super().__init__(section, etype, node)
57 self._entries = OrderedDict()
58 self._pad_byte = 0
59 self._sort = False
60 self._skip_at_start = None
61 self._end_4gb = False
62
63 def ReadNode(self):
64 """Read properties from the section node"""
65 super().ReadNode()
66 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
67 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
68 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
69 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
70 if self._end_4gb:
71 if not self.size:
72 self.Raise("Section size must be provided when using end-at-4gb")
73 if self._skip_at_start is not None:
74 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
75 else:
76 self._skip_at_start = 0x100000000 - self.size
77 else:
78 if self._skip_at_start is None:
79 self._skip_at_start = 0
80 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
81 self.align_default = fdt_util.GetInt(self._node, 'align-default', 0)
82 filename = fdt_util.GetString(self._node, 'filename')
83 if filename:
84 self._filename = filename
85
86 self._ReadEntries()
87
88 def _ReadEntries(self):
89 for node in self._node.subnodes:
90 if node.name.startswith('hash') or node.name.startswith('signature'):
91 continue
92 entry = Entry.Create(self, node,
93 expanded=self.GetImage().use_expanded)
94 entry.ReadNode()
95 entry.SetPrefix(self._name_prefix)
96 self._entries[node.name] = entry
97
98 def _Raise(self, msg):
99 """Raises an error for this section
100
101 Args:
102 msg: Error message to use in the raise string
103 Raises:
104 ValueError()
105 """
106 raise ValueError("Section '%s': %s" % (self._node.path, msg))
107
108 def GetFdts(self):
109 fdts = {}
110 for entry in self._entries.values():
111 fdts.update(entry.GetFdts())
112 return fdts
113
114 def ProcessFdt(self, fdt):
115 """Allow entries to adjust the device tree
116
117 Some entries need to adjust the device tree for their purposes. This
118 may involve adding or deleting properties.
119 """
120 todo = self._entries.values()
121 for passnum in range(3):
122 next_todo = []
123 for entry in todo:
124 if not entry.ProcessFdt(fdt):
125 next_todo.append(entry)
126 todo = next_todo
127 if not todo:
128 break
129 if todo:
130 self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
131 todo)
132 return True
133
134 def ExpandEntries(self):
135 super().ExpandEntries()
136 for entry in self._entries.values():
137 entry.ExpandEntries()
138
139 def AddMissingProperties(self, have_image_pos):
140 """Add new properties to the device tree as needed for this entry"""
141 super().AddMissingProperties(have_image_pos)
142 if self.compress != 'none':
143 have_image_pos = False
144 for entry in self._entries.values():
145 entry.AddMissingProperties(have_image_pos)
146
147 def ObtainContents(self):
148 return self.GetEntryContents()
149
150 def GetPaddedDataForEntry(self, entry, entry_data):
151 """Get the data for an entry including any padding
152
153 Gets the entry data and uses the section pad-byte value to add padding
154 before and after as defined by the pad-before and pad-after properties.
155 This does not consider alignment.
156
157 Args:
158 entry: Entry to check
159
160 Returns:
161 Contents of the entry along with any pad bytes before and
162 after it (bytes)
163 """
164 pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
165 else self._pad_byte)
166
167 data = b''
168 # Handle padding before the entry
169 if entry.pad_before:
170 data += tools.GetBytes(self._pad_byte, entry.pad_before)
171
172 # Add in the actual entry data
173 data += entry_data
174
175 # Handle padding after the entry
176 if entry.pad_after:
177 data += tools.GetBytes(self._pad_byte, entry.pad_after)
178
179 if entry.size:
180 data += tools.GetBytes(pad_byte, entry.size - len(data))
181
182 self.Detail('GetPaddedDataForEntry: size %s' % ToHexSize(self.data))
183
184 return data
185
186 def _BuildSectionData(self, required):
187 """Build the contents of a section
188
189 This places all entries at the right place, dealing with padding before
190 and after entries. It does not do padding for the section itself (the
191 pad-before and pad-after properties in the section items) since that is
192 handled by the parent section.
193
194 Args:
195 required: True if the data must be present, False if it is OK to
196 return None
197
198 Returns:
199 Contents of the section (bytes)
200 """
201 section_data = b''
202
203 for entry in self._entries.values():
204 entry_data = entry.GetData(required)
205 if not required and entry_data is None:
206 return None
207 data = self.GetPaddedDataForEntry(entry, entry_data)
208 # Handle empty space before the entry
209 pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
210 if pad > 0:
211 section_data += tools.GetBytes(self._pad_byte, pad)
212
213 # Add in the actual entry data
214 section_data += data
215
216 self.Detail('GetData: %d entries, total size %#x' %
217 (len(self._entries), len(section_data)))
218 return self.CompressData(section_data)
219
220 def GetPaddedData(self, data=None):
221 """Get the data for a section including any padding
222
223 Gets the section data and uses the parent section's pad-byte value to
224 add padding before and after as defined by the pad-before and pad-after
225 properties. If this is a top-level section (i.e. an image), this is the
226 same as GetData(), since padding is not supported.
227
228 This does not consider alignment.
229
230 Returns:
231 Contents of the section along with any pad bytes before and
232 after it (bytes)
233 """
234 section = self.section or self
235 if data is None:
236 data = self.GetData()
237 return section.GetPaddedDataForEntry(self, data)
238
239 def GetData(self, required=True):
240 """Get the contents of an entry
241
242 This builds the contents of the section, stores this as the contents of
243 the section and returns it
244
245 Args:
246 required: True if the data must be present, False if it is OK to
247 return None
248
249 Returns:
250 bytes content of the section, made up for all all of its subentries.
251 This excludes any padding. If the section is compressed, the
252 compressed data is returned
253 """
254 data = self._BuildSectionData(required)
255 if data is None:
256 return None
257 self.SetContents(data)
258 return data
259
260 def GetOffsets(self):
261 """Handle entries that want to set the offset/size of other entries
262
263 This calls each entry's GetOffsets() method. If it returns a list
264 of entries to update, it updates them.
265 """
266 self.GetEntryOffsets()
267 return {}
268
269 def ResetForPack(self):
270 """Reset offset/size fields so that packing can be done again"""
271 super().ResetForPack()
272 for entry in self._entries.values():
273 entry.ResetForPack()
274
275 def Pack(self, offset):
276 """Pack all entries into the section"""
277 self._PackEntries()
278 if self._sort:
279 self._SortEntries()
280 self._ExpandEntries()
281
282 data = self._BuildSectionData(True)
283 self.SetContents(data)
284
285 self.CheckSize()
286
287 offset = super().Pack(offset)
288 self.CheckEntries()
289 return offset
290
291 def _PackEntries(self):
292 """Pack all entries into the section"""
293 offset = self._skip_at_start
294 for entry in self._entries.values():
295 offset = entry.Pack(offset)
296 return offset
297
298 def _ExpandEntries(self):
299 """Expand any entries that are permitted to"""
300 exp_entry = None
301 for entry in self._entries.values():
302 if exp_entry:
303 exp_entry.ExpandToLimit(entry.offset)
304 exp_entry = None
305 if entry.expand_size:
306 exp_entry = entry
307 if exp_entry:
308 exp_entry.ExpandToLimit(self.size)
309
310 def _SortEntries(self):
311 """Sort entries by offset"""
312 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
313 self._entries.clear()
314 for entry in entries:
315 self._entries[entry._node.name] = entry
316
317 def CheckEntries(self):
318 """Check that entries do not overlap or extend outside the section"""
319 max_size = self.size if self.uncomp_size is None else self.uncomp_size
320
321 offset = 0
322 prev_name = 'None'
323 for entry in self._entries.values():
324 entry.CheckEntries()
325 if (entry.offset < self._skip_at_start or
326 entry.offset + entry.size > self._skip_at_start +
327 max_size):
328 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
329 "section '%s' starting at %#x (%d) "
330 'of size %#x (%d)' %
331 (entry.offset, entry.offset, entry.size, entry.size,
332 self._node.path, self._skip_at_start,
333 self._skip_at_start, max_size, max_size))
334 if entry.offset < offset and entry.size:
335 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
336 "ending at %#x (%d)" %
337 (entry.offset, entry.offset, prev_name, offset, offset))
338 offset = entry.offset + entry.size
339 prev_name = entry.GetPath()
340
341 def WriteSymbols(self, section):
342 """Write symbol values into binary files for access at run time"""
343 for entry in self._entries.values():
344 entry.WriteSymbols(self)
345
346 def SetCalculatedProperties(self):
347 super().SetCalculatedProperties()
348 for entry in self._entries.values():
349 entry.SetCalculatedProperties()
350
351 def SetImagePos(self, image_pos):
352 super().SetImagePos(image_pos)
353 if self.compress == 'none':
354 for entry in self._entries.values():
355 entry.SetImagePos(image_pos + self.offset)
356
357 def ProcessContents(self):
358 sizes_ok_base = super(Entry_section, self).ProcessContents()
359 sizes_ok = True
360 for entry in self._entries.values():
361 if not entry.ProcessContents():
362 sizes_ok = False
363 return sizes_ok and sizes_ok_base
364
365 def WriteMap(self, fd, indent):
366 """Write a map of the section to a .map file
367
368 Args:
369 fd: File to write the map to
370 """
371 Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
372 self.size, self.image_pos)
373 for entry in self._entries.values():
374 entry.WriteMap(fd, indent + 1)
375
376 def GetEntries(self):
377 return self._entries
378
379 def GetContentsByPhandle(self, phandle, source_entry, required):
380 """Get the data contents of an entry specified by a phandle
381
382 This uses a phandle to look up a node and and find the entry
383 associated with it. Then it returns the contents of that entry.
384
385 The node must be a direct subnode of this section.
386
387 Args:
388 phandle: Phandle to look up (integer)
389 source_entry: Entry containing that phandle (used for error
390 reporting)
391 required: True if the data must be present, False if it is OK to
392 return None
393
394 Returns:
395 data from associated entry (as a string), or None if not found
396 """
397 node = self._node.GetFdt().LookupPhandle(phandle)
398 if not node:
399 source_entry.Raise("Cannot find node for phandle %d" % phandle)
400 for entry in self._entries.values():
401 if entry._node == node:
402 return entry.GetData(required)
403 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
404
405 def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None):
406 """Look up a symbol in an ELF file
407
408 Looks up a symbol in an ELF file. Only entry types which come from an
409 ELF image can be used by this function.
410
411 At present the only entry properties supported are:
412 offset
413 image_pos - 'base_addr' is added if this is not an end-at-4gb image
414 size
415
416 Args:
417 sym_name: Symbol name in the ELF file to look up in the format
418 _binman_<entry>_prop_<property> where <entry> is the name of
419 the entry and <property> is the property to find (e.g.
420 _binman_u_boot_prop_offset). As a special case, you can append
421 _any to <entry> to have it search for any matching entry. E.g.
422 _binman_u_boot_any_prop_offset will match entries called u-boot,
423 u-boot-img and u-boot-nodtb)
424 optional: True if the symbol is optional. If False this function
425 will raise if the symbol is not found
426 msg: Message to display if an error occurs
427 base_addr: Base address of image. This is added to the returned
428 image_pos in most cases so that the returned position indicates
429 where the targetted entry/binary has actually been loaded. But
430 if end-at-4gb is used, this is not done, since the binary is
431 already assumed to be linked to the ROM position and using
432 execute-in-place (XIP).
433
434 Returns:
435 Value that should be assigned to that symbol, or None if it was
436 optional and not found
437
438 Raises:
439 ValueError if the symbol is invalid or not found, or references a
440 property which is not supported
441 """
442 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
443 if not m:
444 raise ValueError("%s: Symbol '%s' has invalid format" %
445 (msg, sym_name))
446 entry_name, prop_name = m.groups()
447 entry_name = entry_name.replace('_', '-')
448 if not entries:
449 entries = self._entries
450 entry = entries.get(entry_name)
451 if not entry:
452 if entry_name.endswith('-any'):
453 root = entry_name[:-4]
454 for name in entries:
455 if name.startswith(root):
456 rest = name[len(root):]
457 if rest in ['', '-img', '-nodtb']:
458 entry = entries[name]
459 if not entry:
460 err = ("%s: Entry '%s' not found in list (%s)" %
461 (msg, entry_name, ','.join(entries.keys())))
462 if optional:
463 print('Warning: %s' % err, file=sys.stderr)
464 return None
465 raise ValueError(err)
466 if prop_name == 'offset':
467 return entry.offset
468 elif prop_name == 'image_pos':
469 value = entry.image_pos
470 if not self.GetImage()._end_4gb:
471 value += base_addr
472 return value
473 if prop_name == 'size':
474 return entry.size
475 else:
476 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
477
478 def GetRootSkipAtStart(self):
479 """Get the skip-at-start value for the top-level section
480
481 This is used to find out the starting offset for root section that
482 contains this section. If this is a top-level section then it returns
483 the skip-at-start offset for this section.
484
485 This is used to get the absolute position of section within the image.
486
487 Returns:
488 Integer skip-at-start value for the root section containing this
489 section
490 """
491 if self.section:
492 return self.section.GetRootSkipAtStart()
493 return self._skip_at_start
494
495 def GetStartOffset(self):
496 """Get the start offset for this section
497
498 Returns:
499 The first available offset in this section (typically 0)
500 """
501 return self._skip_at_start
502
503 def GetImageSize(self):
504 """Get the size of the image containing this section
505
506 Returns:
507 Image size as an integer number of bytes, which may be None if the
508 image size is dynamic and its sections have not yet been packed
509 """
510 return self.GetImage().size
511
512 def FindEntryType(self, etype):
513 """Find an entry type in the section
514
515 Args:
516 etype: Entry type to find
517 Returns:
518 entry matching that type, or None if not found
519 """
520 for entry in self._entries.values():
521 if entry.etype == etype:
522 return entry
523 return None
524
525 def GetEntryContents(self):
526 """Call ObtainContents() for each entry in the section
527 """
528 todo = self._entries.values()
529 for passnum in range(3):
530 next_todo = []
531 for entry in todo:
532 if not entry.ObtainContents():
533 next_todo.append(entry)
534 todo = next_todo
535 if not todo:
536 break
537 if todo:
538 self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
539 todo)
540 return True
541
542 def _SetEntryOffsetSize(self, name, offset, size):
543 """Set the offset and size of an entry
544
545 Args:
546 name: Entry name to update
547 offset: New offset, or None to leave alone
548 size: New size, or None to leave alone
549 """
550 entry = self._entries.get(name)
551 if not entry:
552 self._Raise("Unable to set offset/size for unknown entry '%s'" %
553 name)
554 entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
555 else None, size)
556
557 def GetEntryOffsets(self):
558 """Handle entries that want to set the offset/size of other entries
559
560 This calls each entry's GetOffsets() method. If it returns a list
561 of entries to update, it updates them.
562 """
563 for entry in self._entries.values():
564 offset_dict = entry.GetOffsets()
565 for name, info in offset_dict.items():
566 self._SetEntryOffsetSize(name, *info)
567
568 def CheckSize(self):
569 contents_size = len(self.data)
570
571 size = self.size
572 if not size:
573 data = self.GetPaddedData(self.data)
574 size = len(data)
575 size = tools.Align(size, self.align_size)
576
577 if self.size and contents_size > self.size:
578 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
579 (contents_size, contents_size, self.size, self.size))
580 if not self.size:
581 self.size = size
582 if self.size != tools.Align(self.size, self.align_size):
583 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
584 (self.size, self.size, self.align_size,
585 self.align_size))
586 return size
587
588 def ListEntries(self, entries, indent):
589 """List the files in the section"""
590 Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
591 self.image_pos, None, self.offset, self)
592 for entry in self._entries.values():
593 entry.ListEntries(entries, indent + 1)
594
595 def LoadData(self, decomp=True):
596 for entry in self._entries.values():
597 entry.LoadData(decomp)
598 self.Detail('Loaded data')
599
600 def GetImage(self):
601 """Get the image containing this section
602
603 Note that a top-level section is actually an Image, so this function may
604 return self.
605
606 Returns:
607 Image object containing this section
608 """
609 if not self.section:
610 return self
611 return self.section.GetImage()
612
613 def GetSort(self):
614 """Check if the entries in this section will be sorted
615
616 Returns:
617 True if to be sorted, False if entries will be left in the order
618 they appear in the device tree
619 """
620 return self._sort
621
622 def ReadData(self, decomp=True):
623 tout.Info("ReadData path='%s'" % self.GetPath())
624 parent_data = self.section.ReadData(True)
625 offset = self.offset - self.section._skip_at_start
626 data = parent_data[offset:offset + self.size]
627 tout.Info(
628 '%s: Reading data from offset %#x-%#x (real %#x), size %#x, got %#x' %
629 (self.GetPath(), self.offset, self.offset + self.size, offset,
630 self.size, len(data)))
631 return data
632
633 def ReadChildData(self, child, decomp=True):
634 tout.Debug("ReadChildData for child '%s'" % child.GetPath())
635 parent_data = self.ReadData(True)
636 offset = child.offset - self._skip_at_start
637 tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
638 (child.GetPath(), child.offset, self._skip_at_start, offset))
639 data = parent_data[offset:offset + child.size]
640 if decomp:
641 indata = data
642 data = tools.Decompress(indata, child.compress)
643 if child.uncomp_size:
644 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
645 (child.GetPath(), len(indata), child.compress,
646 len(data)))
647 return data
648
649 def WriteChildData(self, child):
650 return True
651
652 def SetAllowMissing(self, allow_missing):
653 """Set whether a section allows missing external blobs
654
655 Args:
656 allow_missing: True if allowed, False if not allowed
657 """
658 self.allow_missing = allow_missing
659 for entry in self._entries.values():
660 entry.SetAllowMissing(allow_missing)
661
662 def CheckMissing(self, missing_list):
663 """Check if any entries in this section have missing external blobs
664
665 If there are missing blobs, the entries are added to the list
666
667 Args:
668 missing_list: List of Entry objects to be added to
669 """
670 for entry in self._entries.values():
671 entry.CheckMissing(missing_list)
672
673 def _CollectEntries(self, entries, entries_by_name, add_entry):
674 """Collect all the entries in an section
675
676 This builds up a dict of entries in this section and all subsections.
677 Entries are indexed by path and by name.
678
679 Since all paths are unique, entries will not have any conflicts. However
680 entries_by_name make have conflicts if two entries have the same name
681 (e.g. with different parent sections). In this case, an entry at a
682 higher level in the hierarchy will win over a lower-level entry.
683
684 Args:
685 entries: dict to put entries:
686 key: entry path
687 value: Entry object
688 entries_by_name: dict to put entries
689 key: entry name
690 value: Entry object
691 add_entry: Entry to add
692 """
693 entries[add_entry.GetPath()] = add_entry
694 to_add = add_entry.GetEntries()
695 if to_add:
696 for entry in to_add.values():
697 entries[entry.GetPath()] = entry
698 for entry in to_add.values():
699 self._CollectEntries(entries, entries_by_name, entry)
700 entries_by_name[add_entry.name] = add_entry
701
702 def MissingArgs(self, entry, missing):
703 """Report a missing argument, if enabled
704
705 For entries which require arguments, this reports an error if some are
706 missing. If missing entries are being ignored (e.g. because we read the
707 entry from an image rather than creating it), this function does
708 nothing.
709
710 Args:
711 missing: List of missing properties / entry args, each a string
712 """
713 if not self._ignore_missing:
714 entry.Raise('Missing required properties/entry args: %s' %
715 (', '.join(missing)))