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