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