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