binman: Enhance the map and fdt-update output
[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 import fdt_util
14 import re
15 import tools
16
17 class Section(object):
18     """A section which contains multiple entries
19
20     A section represents a collection of entries. There must be one or more
21     sections in an image. Sections are used to group entries together.
22
23     Attributes:
24         _node: Node object that contains the section definition in device tree
25         _size: Section size in bytes, or None if not known yet
26         _align_size: Section size alignment, or None
27         _pad_before: Number of bytes before the first entry starts. This
28             effectively changes the place where entry offset 0 starts
29         _pad_after: Number of bytes after the last entry ends. The last
30             entry will finish on or before this boundary
31         _pad_byte: Byte to use to pad the section where there is no entry
32         _sort: True if entries should be sorted by offset, False if they
33             must be in-order in the device tree description
34         _skip_at_start: Number of bytes before the first entry starts. These
35             effectively adjust the starting offset of entries. For example,
36             if _pad_before is 16, then the first entry would start at 16.
37             An entry with offset = 20 would in fact be written at offset 4
38             in the image file.
39         _end_4gb: Indicates that the section ends at the 4GB boundary. This is
40             used for x86 images, which want to use offsets such that a memory
41             address (like 0xff800000) is the first entry offset. This causes
42             _skip_at_start to be set to the starting memory address.
43         _name_prefix: Prefix to add to the name of all entries within this
44             section
45         _entries: OrderedDict() of entries
46     """
47     def __init__(self, name, node, test=False):
48         global entry
49         global Entry
50         import entry
51         from entry import Entry
52
53         self._name = name
54         self._node = node
55         self._offset = 0
56         self._size = None
57         self._align_size = None
58         self._pad_before = 0
59         self._pad_after = 0
60         self._pad_byte = 0
61         self._sort = False
62         self._skip_at_start = 0
63         self._end_4gb = False
64         self._name_prefix = ''
65         self._entries = OrderedDict()
66         if not test:
67             self._ReadNode()
68             self._ReadEntries()
69
70     def _ReadNode(self):
71         """Read properties from the section node"""
72         self._size = fdt_util.GetInt(self._node, 'size')
73         self._align_size = fdt_util.GetInt(self._node, 'align-size')
74         if tools.NotPowerOfTwo(self._align_size):
75             self._Raise("Alignment size %s must be a power of two" %
76                         self._align_size)
77         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
78         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
79         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
80         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
81         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
82         if self._end_4gb and not self._size:
83             self._Raise("Section size must be provided when using end-at-4gb")
84         if self._end_4gb:
85             self._skip_at_start = 0x100000000 - self._size
86         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
87
88     def _ReadEntries(self):
89         for node in self._node.subnodes:
90             entry = Entry.Create(self, node)
91             entry.SetPrefix(self._name_prefix)
92             self._entries[node.name] = entry
93
94     def SetOffset(self, offset):
95         self._offset = offset
96
97     def AddMissingProperties(self):
98         """Add new properties to the device tree as needed for this entry"""
99         for prop in ['offset', 'size']:
100             if not prop in self._node.props:
101                 self._node.AddZeroProp(prop)
102         for entry in self._entries.values():
103             entry.AddMissingProperties()
104
105     def SetCalculatedProperties(self):
106         self._node.SetInt('offset', self._offset)
107         self._node.SetInt('size', self._size)
108         for entry in self._entries.values():
109             entry.SetCalculatedProperties()
110
111     def ProcessFdt(self, fdt):
112         todo = self._entries.values()
113         for passnum in range(3):
114             next_todo = []
115             for entry in todo:
116                 if not entry.ProcessFdt(fdt):
117                     next_todo.append(entry)
118             todo = next_todo
119             if not todo:
120                 break
121         if todo:
122             self._Raise('Internal error: Could not complete processing of Fdt: '
123                         'remaining %s' % todo)
124         return True
125
126     def CheckSize(self):
127         """Check that the section contents does not exceed its size, etc."""
128         contents_size = 0
129         for entry in self._entries.values():
130             contents_size = max(contents_size, entry.offset + entry.size)
131
132         contents_size -= self._skip_at_start
133
134         size = self._size
135         if not size:
136             size = self._pad_before + contents_size + self._pad_after
137             size = tools.Align(size, self._align_size)
138
139         if self._size and contents_size > self._size:
140             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
141                        (contents_size, contents_size, self._size, self._size))
142         if not self._size:
143             self._size = size
144         if self._size != tools.Align(self._size, self._align_size):
145             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
146                   (self._size, self._size, self._align_size, self._align_size))
147         return size
148
149     def _Raise(self, msg):
150         """Raises an error for this section
151
152         Args:
153             msg: Error message to use in the raise string
154         Raises:
155             ValueError()
156         """
157         raise ValueError("Section '%s': %s" % (self._node.path, msg))
158
159     def GetPath(self):
160         """Get the path of an image (in the FDT)
161
162         Returns:
163             Full path of the node for this image
164         """
165         return self._node.path
166
167     def FindEntryType(self, etype):
168         """Find an entry type in the section
169
170         Args:
171             etype: Entry type to find
172         Returns:
173             entry matching that type, or None if not found
174         """
175         for entry in self._entries.values():
176             if entry.etype == etype:
177                 return entry
178         return None
179
180     def GetEntryContents(self):
181         """Call ObtainContents() for each entry
182
183         This calls each entry's ObtainContents() a few times until they all
184         return True. We stop calling an entry's function once it returns
185         True. This allows the contents of one entry to depend on another.
186
187         After 3 rounds we give up since it's likely an error.
188         """
189         todo = self._entries.values()
190         for passnum in range(3):
191             next_todo = []
192             for entry in todo:
193                 if not entry.ObtainContents():
194                     next_todo.append(entry)
195             todo = next_todo
196             if not todo:
197                 break
198         if todo:
199             self._Raise('Internal error: Could not complete processing of '
200                         'contents: remaining %s' % todo)
201         return True
202
203     def _SetEntryOffsetSize(self, name, offset, size):
204         """Set the offset and size of an entry
205
206         Args:
207             name: Entry name to update
208             offset: New offset
209             size: New size
210         """
211         entry = self._entries.get(name)
212         if not entry:
213             self._Raise("Unable to set offset/size for unknown entry '%s'" %
214                         name)
215         entry.SetOffsetSize(self._skip_at_start + offset, size)
216
217     def GetEntryOffsets(self):
218         """Handle entries that want to set the offset/size of other entries
219
220         This calls each entry's GetOffsets() method. If it returns a list
221         of entries to update, it updates them.
222         """
223         for entry in self._entries.values():
224             offset_dict = entry.GetOffsets()
225             for name, info in offset_dict.iteritems():
226                 self._SetEntryOffsetSize(name, *info)
227
228     def PackEntries(self):
229         """Pack all entries into the section"""
230         offset = self._skip_at_start
231         for entry in self._entries.values():
232             offset = entry.Pack(offset)
233         self._size = self.CheckSize()
234
235     def _SortEntries(self):
236         """Sort entries by offset"""
237         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
238         self._entries.clear()
239         for entry in entries:
240             self._entries[entry._node.name] = entry
241
242     def CheckEntries(self):
243         """Check that entries do not overlap or extend outside the section"""
244         if self._sort:
245             self._SortEntries()
246         offset = 0
247         prev_name = 'None'
248         for entry in self._entries.values():
249             entry.CheckOffset()
250             if (entry.offset < self._skip_at_start or
251                 entry.offset >= self._skip_at_start + self._size):
252                 entry.Raise("Offset %#x (%d) is outside the section starting "
253                             "at %#x (%d)" %
254                             (entry.offset, entry.offset, self._skip_at_start,
255                              self._skip_at_start))
256             if entry.offset < offset:
257                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
258                             "ending at %#x (%d)" %
259                             (entry.offset, entry.offset, prev_name, offset, offset))
260             offset = entry.offset + entry.size
261             prev_name = entry.GetPath()
262
263     def ProcessEntryContents(self):
264         """Call the ProcessContents() method for each entry
265
266         This is intended to adjust the contents as needed by the entry type.
267         """
268         for entry in self._entries.values():
269             entry.ProcessContents()
270
271     def WriteSymbols(self):
272         """Write symbol values into binary files for access at run time"""
273         for entry in self._entries.values():
274             entry.WriteSymbols(self)
275
276     def BuildSection(self, fd, base_offset):
277         """Write the section to a file"""
278         fd.seek(base_offset)
279         fd.write(self.GetData())
280
281     def GetData(self):
282         """Get the contents of the section"""
283         section_data = chr(self._pad_byte) * self._size
284
285         for entry in self._entries.values():
286             data = entry.GetData()
287             base = self._pad_before + entry.offset - self._skip_at_start
288             section_data = (section_data[:base] + data +
289                             section_data[base + len(data):])
290         return section_data
291
292     def LookupSymbol(self, sym_name, optional, msg):
293         """Look up a symbol in an ELF file
294
295         Looks up a symbol in an ELF file. Only entry types which come from an
296         ELF image can be used by this function.
297
298         At present the only entry property supported is offset.
299
300         Args:
301             sym_name: Symbol name in the ELF file to look up in the format
302                 _binman_<entry>_prop_<property> where <entry> is the name of
303                 the entry and <property> is the property to find (e.g.
304                 _binman_u_boot_prop_offset). As a special case, you can append
305                 _any to <entry> to have it search for any matching entry. E.g.
306                 _binman_u_boot_any_prop_offset will match entries called u-boot,
307                 u-boot-img and u-boot-nodtb)
308             optional: True if the symbol is optional. If False this function
309                 will raise if the symbol is not found
310             msg: Message to display if an error occurs
311
312         Returns:
313             Value that should be assigned to that symbol, or None if it was
314                 optional and not found
315
316         Raises:
317             ValueError if the symbol is invalid or not found, or references a
318                 property which is not supported
319         """
320         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
321         if not m:
322             raise ValueError("%s: Symbol '%s' has invalid format" %
323                              (msg, sym_name))
324         entry_name, prop_name = m.groups()
325         entry_name = entry_name.replace('_', '-')
326         entry = self._entries.get(entry_name)
327         if not entry:
328             if entry_name.endswith('-any'):
329                 root = entry_name[:-4]
330                 for name in self._entries:
331                     if name.startswith(root):
332                         rest = name[len(root):]
333                         if rest in ['', '-img', '-nodtb']:
334                             entry = self._entries[name]
335         if not entry:
336             err = ("%s: Entry '%s' not found in list (%s)" %
337                    (msg, entry_name, ','.join(self._entries.keys())))
338             if optional:
339                 print('Warning: %s' % err, file=sys.stderr)
340                 return None
341             raise ValueError(err)
342         if prop_name == 'offset':
343             return entry.offset
344         else:
345             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
346
347     def GetEntries(self):
348         """Get the number of entries in a section
349
350         Returns:
351             Number of entries in a section
352         """
353         return self._entries
354
355     def GetSize(self):
356         """Get the size of a section in bytes
357
358         This is only meaningful if the section has a pre-defined size, or the
359         entries within it have been packed, so that the size has been
360         calculated.
361
362         Returns:
363             Entry size in bytes
364         """
365         return self._size
366
367     def WriteMap(self, fd, indent):
368         """Write a map of the section to a .map file
369
370         Args:
371             fd: File to write the map to
372         """
373         Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size)
374         for entry in self._entries.values():
375             entry.WriteMap(fd, indent + 1)