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