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