082f424241cc8dc55539117c15568b9c6cdf9dca
[oweals/u-boot.git] / tools / binman / bsection.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Base class for sections (collections of entries)
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import sys
12
13 from entry import Entry
14 import fdt_util
15 import re
16 import state
17 import tools
18
19 class Section(object):
20     """A section which contains multiple entries
21
22     A section represents a collection of entries. There must be one or more
23     sections in an image. Sections are used to group entries together.
24
25     Attributes:
26         _node: Node object that contains the section definition in device tree
27         _parent_section: Parent Section object which created this Section
28         _size: Section size in bytes, or None if not known yet
29         _align_size: Section size alignment, or None
30         _pad_before: Number of bytes before the first entry starts. This
31             effectively changes the place where entry offset 0 starts
32         _pad_after: Number of bytes after the last entry ends. The last
33             entry will finish on or before this boundary
34         _pad_byte: Byte to use to pad the section where there is no entry
35         _sort: True if entries should be sorted by offset, False if they
36             must be in-order in the device tree description
37         _skip_at_start: Number of bytes before the first entry starts. These
38             effectively adjust the starting offset of entries. For example,
39             if _pad_before is 16, then the first entry would start at 16.
40             An entry with offset = 20 would in fact be written at offset 4
41             in the image file.
42         _end_4gb: Indicates that the section ends at the 4GB boundary. This is
43             used for x86 images, which want to use offsets such that a memory
44             address (like 0xff800000) is the first entry offset. This causes
45             _skip_at_start to be set to the starting memory address.
46         _name_prefix: Prefix to add to the name of all entries within this
47             section
48         _entries: OrderedDict() of entries
49         _orig_offset: Original offset value read from node
50         _orig_size: Original size value read from node
51     """
52     def __init__(self, name, parent_section, node, image, test=False):
53         global entry
54         global Entry
55         import entry
56         from entry import Entry
57
58         self._parent_section = parent_section
59         self._name = name
60         self._node = node
61         self._image = image
62         self._offset = None
63         self._size = None
64         self._align_size = None
65         self._pad_before = 0
66         self._pad_after = 0
67         self._pad_byte = 0
68         self._sort = False
69         self._skip_at_start = None
70         self._end_4gb = False
71         self._name_prefix = ''
72         self._entries = OrderedDict()
73         self._image_pos = None
74         if not test:
75             self._ReadNode()
76             self._ReadEntries()
77
78     def _ReadNode(self):
79         """Read properties from the section node"""
80         self._offset = fdt_util.GetInt(self._node, 'offset')
81         self._size = fdt_util.GetInt(self._node, 'size')
82         self._orig_offset = self._offset
83         self._orig_size = self._size
84         self._align_size = fdt_util.GetInt(self._node, 'align-size')
85         if tools.NotPowerOfTwo(self._align_size):
86             self._Raise("Alignment size %s must be a power of two" %
87                         self._align_size)
88         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
89         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
90         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
91         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
92         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
93         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
94         if self._end_4gb:
95             if not self._size:
96                 self._Raise("Section size must be provided when using end-at-4gb")
97             if self._skip_at_start is not None:
98                 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
99             else:
100                 self._skip_at_start = 0x100000000 - self._size
101         else:
102             if self._skip_at_start is None:
103                 self._skip_at_start = 0
104         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
105
106     def _ReadEntries(self):
107         for node in self._node.subnodes:
108             if node.name == 'hash':
109                 continue
110             entry = Entry.Create(self, node)
111             entry.SetPrefix(self._name_prefix)
112             self._entries[node.name] = entry
113
114     def GetFdtSet(self):
115         """Get the set of device tree files used by this image"""
116         fdt_set = set()
117         for entry in self._entries.values():
118             fdt_set.update(entry.GetFdtSet())
119         return fdt_set
120
121     def SetOffset(self, offset):
122         self._offset = offset
123
124     def ExpandEntries(self):
125         for entry in self._entries.values():
126             entry.ExpandEntries()
127
128     def AddMissingProperties(self):
129         """Add new properties to the device tree as needed for this entry"""
130         for prop in ['offset', 'size', 'image-pos']:
131             if not prop in self._node.props:
132                 state.AddZeroProp(self._node, prop)
133         state.CheckAddHashProp(self._node)
134         for entry in self._entries.values():
135             entry.AddMissingProperties()
136
137     def SetCalculatedProperties(self):
138         state.SetInt(self._node, 'offset', self._offset or 0)
139         state.SetInt(self._node, 'size', self._size)
140         image_pos = self._image_pos
141         if self._parent_section:
142             image_pos -= self._parent_section.GetRootSkipAtStart()
143         state.SetInt(self._node, 'image-pos', image_pos)
144         for entry in self._entries.values():
145             entry.SetCalculatedProperties()
146
147     def ProcessFdt(self, fdt):
148         todo = self._entries.values()
149         for passnum in range(3):
150             next_todo = []
151             for entry in todo:
152                 if not entry.ProcessFdt(fdt):
153                     next_todo.append(entry)
154             todo = next_todo
155             if not todo:
156                 break
157         if todo:
158             self._Raise('Internal error: Could not complete processing of Fdt: '
159                         'remaining %s' % todo)
160         return True
161
162     def CheckSize(self):
163         """Check that the section contents does not exceed its size, etc."""
164         contents_size = 0
165         for entry in self._entries.values():
166             contents_size = max(contents_size, entry.offset + entry.size)
167
168         contents_size -= self._skip_at_start
169
170         size = self._size
171         if not size:
172             size = self._pad_before + contents_size + self._pad_after
173             size = tools.Align(size, self._align_size)
174
175         if self._size and contents_size > self._size:
176             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
177                        (contents_size, contents_size, self._size, self._size))
178         if not self._size:
179             self._size = size
180         if self._size != tools.Align(self._size, self._align_size):
181             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
182                   (self._size, self._size, self._align_size, self._align_size))
183         return size
184
185     def _Raise(self, msg):
186         """Raises an error for this section
187
188         Args:
189             msg: Error message to use in the raise string
190         Raises:
191             ValueError()
192         """
193         raise ValueError("Section '%s': %s" % (self._node.path, msg))
194
195     def GetPath(self):
196         """Get the path of an image (in the FDT)
197
198         Returns:
199             Full path of the node for this image
200         """
201         return self._node.path
202
203     def FindEntryType(self, etype):
204         """Find an entry type in the section
205
206         Args:
207             etype: Entry type to find
208         Returns:
209             entry matching that type, or None if not found
210         """
211         for entry in self._entries.values():
212             if entry.etype == etype:
213                 return entry
214         return None
215
216     def GetEntryContents(self):
217         """Call ObtainContents() for each entry
218
219         This calls each entry's ObtainContents() a few times until they all
220         return True. We stop calling an entry's function once it returns
221         True. This allows the contents of one entry to depend on another.
222
223         After 3 rounds we give up since it's likely an error.
224         """
225         todo = self._entries.values()
226         for passnum in range(3):
227             next_todo = []
228             for entry in todo:
229                 if not entry.ObtainContents():
230                     next_todo.append(entry)
231             todo = next_todo
232             if not todo:
233                 break
234         if todo:
235             self._Raise('Internal error: Could not complete processing of '
236                         'contents: remaining %s' % todo)
237         return True
238
239     def _SetEntryOffsetSize(self, name, offset, size):
240         """Set the offset and size of an entry
241
242         Args:
243             name: Entry name to update
244             offset: New offset, or None to leave alone
245             size: New size, or None to leave alone
246         """
247         entry = self._entries.get(name)
248         if not entry:
249             self._Raise("Unable to set offset/size for unknown entry '%s'" %
250                         name)
251         entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
252                             size)
253
254     def GetEntryOffsets(self):
255         """Handle entries that want to set the offset/size of other entries
256
257         This calls each entry's GetOffsets() method. If it returns a list
258         of entries to update, it updates them.
259         """
260         for entry in self._entries.values():
261             offset_dict = entry.GetOffsets()
262             for name, info in offset_dict.items():
263                 self._SetEntryOffsetSize(name, *info)
264
265     def ResetForPack(self):
266         """Reset offset/size fields so that packing can be done again"""
267         self._offset = self._orig_offset
268         self._size = self._orig_size
269         for entry in self._entries.values():
270             entry.ResetForPack()
271
272     def PackEntries(self):
273         """Pack all entries into the section"""
274         offset = self._skip_at_start
275         for entry in self._entries.values():
276             offset = entry.Pack(offset)
277         self._size = self.CheckSize()
278
279     def _SortEntries(self):
280         """Sort entries by offset"""
281         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
282         self._entries.clear()
283         for entry in entries:
284             self._entries[entry._node.name] = entry
285
286     def _ExpandEntries(self):
287         """Expand any entries that are permitted to"""
288         exp_entry = None
289         for entry in self._entries.values():
290             if exp_entry:
291                 exp_entry.ExpandToLimit(entry.offset)
292                 exp_entry = None
293             if entry.expand_size:
294                 exp_entry = entry
295         if exp_entry:
296             exp_entry.ExpandToLimit(self._size)
297
298     def CheckEntries(self):
299         """Check that entries do not overlap or extend outside the section
300
301         This also sorts entries, if needed and expands
302         """
303         if self._sort:
304             self._SortEntries()
305         self._ExpandEntries()
306         offset = 0
307         prev_name = 'None'
308         for entry in self._entries.values():
309             entry.CheckOffset()
310             if (entry.offset < self._skip_at_start or
311                 entry.offset + entry.size > self._skip_at_start + self._size):
312                 entry.Raise("Offset %#x (%d) is outside the section starting "
313                             "at %#x (%d)" %
314                             (entry.offset, entry.offset, self._skip_at_start,
315                              self._skip_at_start))
316             if entry.offset < offset:
317                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
318                             "ending at %#x (%d)" %
319                             (entry.offset, entry.offset, prev_name, offset, offset))
320             offset = entry.offset + entry.size
321             prev_name = entry.GetPath()
322
323     def SetImagePos(self, image_pos):
324         self._image_pos = image_pos
325         for entry in self._entries.values():
326             entry.SetImagePos(image_pos)
327
328     def ProcessEntryContents(self):
329         """Call the ProcessContents() method for each entry
330
331         This is intended to adjust the contents as needed by the entry type.
332
333         Returns:
334             True if no entries needed to change their size
335         """
336         sizes_ok = True
337         for entry in self._entries.values():
338             if not entry.ProcessContents():
339                 sizes_ok = False
340                 print("Entry '%s' size change" % self._node.path)
341         return sizes_ok
342
343     def WriteSymbols(self):
344         """Write symbol values into binary files for access at run time"""
345         for entry in self._entries.values():
346             entry.WriteSymbols(self)
347
348     def BuildSection(self, fd, base_offset):
349         """Write the section to a file"""
350         fd.seek(base_offset)
351         fd.write(self.GetData())
352
353     def GetData(self):
354         """Get the contents of the section"""
355         section_data = tools.GetBytes(self._pad_byte, self._size)
356
357         for entry in self._entries.values():
358             data = entry.GetData()
359             base = self._pad_before + entry.offset - self._skip_at_start
360             section_data = (section_data[:base] + data +
361                             section_data[base + len(data):])
362         return section_data
363
364     def LookupSymbol(self, sym_name, optional, msg):
365         """Look up a symbol in an ELF file
366
367         Looks up a symbol in an ELF file. Only entry types which come from an
368         ELF image can be used by this function.
369
370         At present the only entry property supported is offset.
371
372         Args:
373             sym_name: Symbol name in the ELF file to look up in the format
374                 _binman_<entry>_prop_<property> where <entry> is the name of
375                 the entry and <property> is the property to find (e.g.
376                 _binman_u_boot_prop_offset). As a special case, you can append
377                 _any to <entry> to have it search for any matching entry. E.g.
378                 _binman_u_boot_any_prop_offset will match entries called u-boot,
379                 u-boot-img and u-boot-nodtb)
380             optional: True if the symbol is optional. If False this function
381                 will raise if the symbol is not found
382             msg: Message to display if an error occurs
383
384         Returns:
385             Value that should be assigned to that symbol, or None if it was
386                 optional and not found
387
388         Raises:
389             ValueError if the symbol is invalid or not found, or references a
390                 property which is not supported
391         """
392         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
393         if not m:
394             raise ValueError("%s: Symbol '%s' has invalid format" %
395                              (msg, sym_name))
396         entry_name, prop_name = m.groups()
397         entry_name = entry_name.replace('_', '-')
398         entry = self._entries.get(entry_name)
399         if not entry:
400             if entry_name.endswith('-any'):
401                 root = entry_name[:-4]
402                 for name in self._entries:
403                     if name.startswith(root):
404                         rest = name[len(root):]
405                         if rest in ['', '-img', '-nodtb']:
406                             entry = self._entries[name]
407         if not entry:
408             err = ("%s: Entry '%s' not found in list (%s)" %
409                    (msg, entry_name, ','.join(self._entries.keys())))
410             if optional:
411                 print('Warning: %s' % err, file=sys.stderr)
412                 return None
413             raise ValueError(err)
414         if prop_name == 'offset':
415             return entry.offset
416         elif prop_name == 'image_pos':
417             return entry.image_pos
418         else:
419             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
420
421     def GetEntries(self):
422         """Get the dict of entries in a section
423
424         Returns:
425             OrderedDict of entries in a section
426         """
427         return self._entries
428
429     def GetSize(self):
430         """Get the size of a section in bytes
431
432         This is only meaningful if the section has a pre-defined size, or the
433         entries within it have been packed, so that the size has been
434         calculated.
435
436         Returns:
437             Entry size in bytes
438         """
439         return self._size
440
441     def WriteMap(self, fd, indent):
442         """Write a map of the section to a .map file
443
444         Args:
445             fd: File to write the map to
446         """
447         Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
448                            self._size, self._image_pos)
449         for entry in self._entries.values():
450             entry.WriteMap(fd, indent + 1)
451
452     def GetContentsByPhandle(self, phandle, source_entry):
453         """Get the data contents of an entry specified by a phandle
454
455         This uses a phandle to look up a node and and find the entry
456         associated with it. Then it returnst he contents of that entry.
457
458         Args:
459             phandle: Phandle to look up (integer)
460             source_entry: Entry containing that phandle (used for error
461                 reporting)
462
463         Returns:
464             data from associated entry (as a string), or None if not found
465         """
466         node = self._node.GetFdt().LookupPhandle(phandle)
467         if not node:
468             source_entry.Raise("Cannot find node for phandle %d" % phandle)
469         for entry in self._entries.values():
470             if entry._node == node:
471                 return entry.GetData()
472         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
473
474     def ExpandSize(self, size):
475         """Change the size of an entry
476
477         Args:
478             size: New size for entry
479         """
480         if size != self._size:
481             self._size = size
482
483     def GetRootSkipAtStart(self):
484         """Get the skip-at-start value for the top-level section
485
486         This is used to find out the starting offset for root section that
487         contains this section. If this is a top-level section then it returns
488         the skip-at-start offset for this section.
489
490         This is used to get the absolute position of section within the image.
491
492         Returns:
493             Integer skip-at-start value for the root section containing this
494                 section
495         """
496         if self._parent_section:
497             return self._parent_section.GetRootSkipAtStart()
498         return self._skip_at_start
499
500     def GetStartOffset(self):
501         """Get the start offset for this section
502
503         Returns:
504             The first available offset in this section (typically 0)
505         """
506         return self._skip_at_start
507
508     def GetImageSize(self):
509         """Get the size of the image containing this section
510
511         Returns:
512             Image size as an integer number of bytes, which may be None if the
513                 image size is dynamic and its sections have not yet been packed
514         """
515         return self._image._size
516
517     def ListEntries(self, entries, indent):
518         """Override this method to list all files in the section"""
519         Entry.AddEntryInfo(entries, indent, self._name, 'section', self._size,
520                            self._image_pos, None, self._offset,
521                            self._parent_section)
522         for entry in self._entries.values():
523             entry.ListEntries(entries, indent + 1)