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