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