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