binman: Correct fmap output on x86
[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 from sets import Set
12 import sys
13
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     """
50     def __init__(self, name, parent_section, node, image, test=False):
51         global entry
52         global Entry
53         import entry
54         from entry import Entry
55
56         self._parent_section = parent_section
57         self._name = name
58         self._node = node
59         self._image = image
60         self._offset = 0
61         self._size = None
62         self._align_size = None
63         self._pad_before = 0
64         self._pad_after = 0
65         self._pad_byte = 0
66         self._sort = False
67         self._skip_at_start = 0
68         self._end_4gb = False
69         self._name_prefix = ''
70         self._entries = OrderedDict()
71         self._image_pos = None
72         if not test:
73             self._ReadNode()
74             self._ReadEntries()
75
76     def _ReadNode(self):
77         """Read properties from the section node"""
78         self._size = fdt_util.GetInt(self._node, 'size')
79         self._align_size = fdt_util.GetInt(self._node, 'align-size')
80         if tools.NotPowerOfTwo(self._align_size):
81             self._Raise("Alignment size %s must be a power of two" %
82                         self._align_size)
83         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
84         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
85         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
86         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
87         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
88         if self._end_4gb and not self._size:
89             self._Raise("Section size must be provided when using end-at-4gb")
90         if self._end_4gb:
91             self._skip_at_start = 0x100000000 - self._size
92         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
93
94     def _ReadEntries(self):
95         for node in self._node.subnodes:
96             if node.name == 'hash':
97                 continue
98             entry = Entry.Create(self, node)
99             entry.SetPrefix(self._name_prefix)
100             self._entries[node.name] = entry
101
102     def GetFdtSet(self):
103         """Get the set of device tree files used by this image"""
104         fdt_set = Set()
105         for entry in self._entries.values():
106             fdt_set.update(entry.GetFdtSet())
107         return fdt_set
108
109     def SetOffset(self, offset):
110         self._offset = offset
111
112     def ExpandEntries(self):
113         for entry in self._entries.values():
114             entry.ExpandEntries()
115
116     def AddMissingProperties(self):
117         """Add new properties to the device tree as needed for this entry"""
118         for prop in ['offset', 'size', 'image-pos']:
119             if not prop in self._node.props:
120                 state.AddZeroProp(self._node, prop)
121         state.CheckAddHashProp(self._node)
122         for entry in self._entries.values():
123             entry.AddMissingProperties()
124
125     def SetCalculatedProperties(self):
126         state.SetInt(self._node, 'offset', self._offset)
127         state.SetInt(self._node, 'size', self._size)
128         image_pos = self._image_pos
129         if self._parent_section:
130             image_pos -= self._parent_section.GetRootSkipAtStart()
131         state.SetInt(self._node, 'image-pos', image_pos)
132         for entry in self._entries.values():
133             entry.SetCalculatedProperties()
134
135     def ProcessFdt(self, fdt):
136         todo = self._entries.values()
137         for passnum in range(3):
138             next_todo = []
139             for entry in todo:
140                 if not entry.ProcessFdt(fdt):
141                     next_todo.append(entry)
142             todo = next_todo
143             if not todo:
144                 break
145         if todo:
146             self._Raise('Internal error: Could not complete processing of Fdt: '
147                         'remaining %s' % todo)
148         return True
149
150     def CheckSize(self):
151         """Check that the section contents does not exceed its size, etc."""
152         contents_size = 0
153         for entry in self._entries.values():
154             contents_size = max(contents_size, entry.offset + entry.size)
155
156         contents_size -= self._skip_at_start
157
158         size = self._size
159         if not size:
160             size = self._pad_before + contents_size + self._pad_after
161             size = tools.Align(size, self._align_size)
162
163         if self._size and contents_size > self._size:
164             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
165                        (contents_size, contents_size, self._size, self._size))
166         if not self._size:
167             self._size = size
168         if self._size != tools.Align(self._size, self._align_size):
169             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
170                   (self._size, self._size, self._align_size, self._align_size))
171         return size
172
173     def _Raise(self, msg):
174         """Raises an error for this section
175
176         Args:
177             msg: Error message to use in the raise string
178         Raises:
179             ValueError()
180         """
181         raise ValueError("Section '%s': %s" % (self._node.path, msg))
182
183     def GetPath(self):
184         """Get the path of an image (in the FDT)
185
186         Returns:
187             Full path of the node for this image
188         """
189         return self._node.path
190
191     def FindEntryType(self, etype):
192         """Find an entry type in the section
193
194         Args:
195             etype: Entry type to find
196         Returns:
197             entry matching that type, or None if not found
198         """
199         for entry in self._entries.values():
200             if entry.etype == etype:
201                 return entry
202         return None
203
204     def GetEntryContents(self):
205         """Call ObtainContents() for each entry
206
207         This calls each entry's ObtainContents() a few times until they all
208         return True. We stop calling an entry's function once it returns
209         True. This allows the contents of one entry to depend on another.
210
211         After 3 rounds we give up since it's likely an error.
212         """
213         todo = self._entries.values()
214         for passnum in range(3):
215             next_todo = []
216             for entry in todo:
217                 if not entry.ObtainContents():
218                     next_todo.append(entry)
219             todo = next_todo
220             if not todo:
221                 break
222         if todo:
223             self._Raise('Internal error: Could not complete processing of '
224                         'contents: remaining %s' % todo)
225         return True
226
227     def _SetEntryOffsetSize(self, name, offset, size):
228         """Set the offset and size of an entry
229
230         Args:
231             name: Entry name to update
232             offset: New offset
233             size: New size
234         """
235         entry = self._entries.get(name)
236         if not entry:
237             self._Raise("Unable to set offset/size for unknown entry '%s'" %
238                         name)
239         entry.SetOffsetSize(self._skip_at_start + offset, size)
240
241     def GetEntryOffsets(self):
242         """Handle entries that want to set the offset/size of other entries
243
244         This calls each entry's GetOffsets() method. If it returns a list
245         of entries to update, it updates them.
246         """
247         for entry in self._entries.values():
248             offset_dict = entry.GetOffsets()
249             for name, info in offset_dict.iteritems():
250                 self._SetEntryOffsetSize(name, *info)
251
252     def PackEntries(self):
253         """Pack all entries into the section"""
254         offset = self._skip_at_start
255         for entry in self._entries.values():
256             offset = entry.Pack(offset)
257         self._size = self.CheckSize()
258
259     def _SortEntries(self):
260         """Sort entries by offset"""
261         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
262         self._entries.clear()
263         for entry in entries:
264             self._entries[entry._node.name] = entry
265
266     def _ExpandEntries(self):
267         """Expand any entries that are permitted to"""
268         exp_entry = None
269         for entry in self._entries.values():
270             if exp_entry:
271                 exp_entry.ExpandToLimit(entry.offset)
272                 exp_entry = None
273             if entry.expand_size:
274                 exp_entry = entry
275         if exp_entry:
276             exp_entry.ExpandToLimit(self._size)
277
278     def CheckEntries(self):
279         """Check that entries do not overlap or extend outside the section
280
281         This also sorts entries, if needed and expands
282         """
283         if self._sort:
284             self._SortEntries()
285         self._ExpandEntries()
286         offset = 0
287         prev_name = 'None'
288         for entry in self._entries.values():
289             entry.CheckOffset()
290             if (entry.offset < self._skip_at_start or
291                 entry.offset + entry.size > self._skip_at_start + self._size):
292                 entry.Raise("Offset %#x (%d) is outside the section starting "
293                             "at %#x (%d)" %
294                             (entry.offset, entry.offset, self._skip_at_start,
295                              self._skip_at_start))
296             if entry.offset < offset:
297                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
298                             "ending at %#x (%d)" %
299                             (entry.offset, entry.offset, prev_name, offset, offset))
300             offset = entry.offset + entry.size
301             prev_name = entry.GetPath()
302
303     def SetImagePos(self, image_pos):
304         self._image_pos = image_pos
305         for entry in self._entries.values():
306             entry.SetImagePos(image_pos)
307
308     def ProcessEntryContents(self):
309         """Call the ProcessContents() method for each entry
310
311         This is intended to adjust the contents as needed by the entry type.
312         """
313         for entry in self._entries.values():
314             entry.ProcessContents()
315
316     def WriteSymbols(self):
317         """Write symbol values into binary files for access at run time"""
318         for entry in self._entries.values():
319             entry.WriteSymbols(self)
320
321     def BuildSection(self, fd, base_offset):
322         """Write the section to a file"""
323         fd.seek(base_offset)
324         fd.write(self.GetData())
325
326     def GetData(self):
327         """Get the contents of the section"""
328         section_data = chr(self._pad_byte) * self._size
329
330         for entry in self._entries.values():
331             data = entry.GetData()
332             base = self._pad_before + entry.offset - self._skip_at_start
333             section_data = (section_data[:base] + data +
334                             section_data[base + len(data):])
335         return section_data
336
337     def LookupSymbol(self, sym_name, optional, msg):
338         """Look up a symbol in an ELF file
339
340         Looks up a symbol in an ELF file. Only entry types which come from an
341         ELF image can be used by this function.
342
343         At present the only entry property supported is offset.
344
345         Args:
346             sym_name: Symbol name in the ELF file to look up in the format
347                 _binman_<entry>_prop_<property> where <entry> is the name of
348                 the entry and <property> is the property to find (e.g.
349                 _binman_u_boot_prop_offset). As a special case, you can append
350                 _any to <entry> to have it search for any matching entry. E.g.
351                 _binman_u_boot_any_prop_offset will match entries called u-boot,
352                 u-boot-img and u-boot-nodtb)
353             optional: True if the symbol is optional. If False this function
354                 will raise if the symbol is not found
355             msg: Message to display if an error occurs
356
357         Returns:
358             Value that should be assigned to that symbol, or None if it was
359                 optional and not found
360
361         Raises:
362             ValueError if the symbol is invalid or not found, or references a
363                 property which is not supported
364         """
365         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
366         if not m:
367             raise ValueError("%s: Symbol '%s' has invalid format" %
368                              (msg, sym_name))
369         entry_name, prop_name = m.groups()
370         entry_name = entry_name.replace('_', '-')
371         entry = self._entries.get(entry_name)
372         if not entry:
373             if entry_name.endswith('-any'):
374                 root = entry_name[:-4]
375                 for name in self._entries:
376                     if name.startswith(root):
377                         rest = name[len(root):]
378                         if rest in ['', '-img', '-nodtb']:
379                             entry = self._entries[name]
380         if not entry:
381             err = ("%s: Entry '%s' not found in list (%s)" %
382                    (msg, entry_name, ','.join(self._entries.keys())))
383             if optional:
384                 print('Warning: %s' % err, file=sys.stderr)
385                 return None
386             raise ValueError(err)
387         if prop_name == 'offset':
388             return entry.offset
389         elif prop_name == 'image_pos':
390             return entry.image_pos
391         else:
392             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
393
394     def GetEntries(self):
395         """Get the number of entries in a section
396
397         Returns:
398             Number of entries in a section
399         """
400         return self._entries
401
402     def GetSize(self):
403         """Get the size of a section in bytes
404
405         This is only meaningful if the section has a pre-defined size, or the
406         entries within it have been packed, so that the size has been
407         calculated.
408
409         Returns:
410             Entry size in bytes
411         """
412         return self._size
413
414     def WriteMap(self, fd, indent):
415         """Write a map of the section to a .map file
416
417         Args:
418             fd: File to write the map to
419         """
420         Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
421                            self._image_pos)
422         for entry in self._entries.values():
423             entry.WriteMap(fd, indent + 1)
424
425     def GetContentsByPhandle(self, phandle, source_entry):
426         """Get the data contents of an entry specified by a phandle
427
428         This uses a phandle to look up a node and and find the entry
429         associated with it. Then it returnst he contents of that entry.
430
431         Args:
432             phandle: Phandle to look up (integer)
433             source_entry: Entry containing that phandle (used for error
434                 reporting)
435
436         Returns:
437             data from associated entry (as a string), or None if not found
438         """
439         node = self._node.GetFdt().LookupPhandle(phandle)
440         if not node:
441             source_entry.Raise("Cannot find node for phandle %d" % phandle)
442         for entry in self._entries.values():
443             if entry._node == node:
444                 return entry.GetData()
445         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
446
447     def ExpandSize(self, size):
448         if size != self._size:
449             self._size = size
450
451     def GetRootSkipAtStart(self):
452         if self._parent_section:
453             return self._parent_section.GetRootSkipAtStart()
454         return self._skip_at_start
455
456     def GetImageSize(self):
457         return self._image._size