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