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