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