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