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