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