Merge tag 'dm-pull-24jul19-take3' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[platform/kernel/u-boot.git] / tools / binman / etype / section.py
1 # SPDX-License-Identifier:      GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4
5 """Entry-type module for sections (groups of entries)
6
7 Sections are entries which can contain other entries. This allows hierarchical
8 images to be created.
9 """
10
11 from __future__ import print_function
12
13 from collections import OrderedDict
14 import re
15 import sys
16
17 from entry import Entry
18 import fdt_util
19 import tools
20
21
22 class Entry_section(Entry):
23     """Entry that contains other entries
24
25     Properties / Entry arguments: (see binman README for more information)
26         pad-byte: Pad byte to use when padding
27         sort-by-offset: True if entries should be sorted by offset, False if
28             they must be in-order in the device tree description
29         end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
30         skip-at-start: Number of bytes before the first entry starts. These
31             effectively adjust the starting offset of entries. For example,
32             if this is 16, then the first entry would start at 16. An entry
33             with offset = 20 would in fact be written at offset 4 in the image
34             file, since the first 16 bytes are skipped when writing.
35         name-prefix: Adds a prefix to the name of every entry in the section
36             when writing out the map
37
38     Since a section is also an entry, it inherits all the properies of entries
39     too.
40
41     A section is an entry which can contain other entries, thus allowing
42     hierarchical images to be created. See 'Sections and hierarchical images'
43     in the binman README for more information.
44     """
45     def __init__(self, section, etype, node, test=False):
46         if not test:
47             Entry.__init__(self, section, etype, node)
48         if section:
49             self.image = section.image
50         self._entries = OrderedDict()
51         self._pad_byte = 0
52         self._sort = False
53         self._skip_at_start = None
54         self._end_4gb = False
55         if not test:
56             self._ReadNode()
57             self._ReadEntries()
58
59     def _Raise(self, msg):
60         """Raises an error for this section
61
62         Args:
63             msg: Error message to use in the raise string
64         Raises:
65             ValueError()
66         """
67         raise ValueError("Section '%s': %s" % (self._node.path, msg))
68
69     def _ReadNode(self):
70         """Read properties from the image node"""
71         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
72         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
73         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
74         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
75         if self._end_4gb:
76             if not self.size:
77                 self.Raise("Section size must be provided when using end-at-4gb")
78             if self._skip_at_start is not None:
79                 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
80             else:
81                 self._skip_at_start = 0x100000000 - self.size
82         else:
83             if self._skip_at_start is None:
84                 self._skip_at_start = 0
85         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
86         filename = fdt_util.GetString(self._node, 'filename')
87         if filename:
88             self._filename = filename
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         fdt_set = set()
100         for entry in self._entries.values():
101             fdt_set.update(entry.GetFdtSet())
102         return fdt_set
103
104     def ProcessFdt(self, fdt):
105         """Allow entries to adjust the device tree
106
107         Some entries need to adjust the device tree for their purposes. This
108         may involve adding or deleting properties.
109         """
110         todo = self._entries.values()
111         for passnum in range(3):
112             next_todo = []
113             for entry in todo:
114                 if not entry.ProcessFdt(fdt):
115                     next_todo.append(entry)
116             todo = next_todo
117             if not todo:
118                 break
119         if todo:
120             self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
121                        todo)
122         return True
123
124     def ExpandEntries(self):
125         """Expand out any entries which have calculated sub-entries
126
127         Some entries are expanded out at runtime, e.g. 'files', which produces
128         a section containing a list of files. Process these entries so that
129         this information is added to the device tree.
130         """
131         Entry.ExpandEntries(self)
132         for entry in self._entries.values():
133             entry.ExpandEntries()
134
135     def AddMissingProperties(self):
136         """Add new properties to the device tree as needed for this entry"""
137         Entry.AddMissingProperties(self)
138         for entry in self._entries.values():
139             entry.AddMissingProperties()
140
141     def ObtainContents(self):
142         return self.GetEntryContents()
143
144     def GetData(self):
145         section_data = tools.GetBytes(self._pad_byte, self.size)
146
147         for entry in self._entries.values():
148             data = entry.GetData()
149             base = self.pad_before + entry.offset - self._skip_at_start
150             section_data = (section_data[:base] + data +
151                             section_data[base + len(data):])
152         return section_data
153
154     def GetOffsets(self):
155         """Handle entries that want to set the offset/size of other entries
156
157         This calls each entry's GetOffsets() method. If it returns a list
158         of entries to update, it updates them.
159         """
160         self.GetEntryOffsets()
161         return {}
162
163     def ResetForPack(self):
164         """Reset offset/size fields so that packing can be done again"""
165         Entry.ResetForPack(self)
166         for entry in self._entries.values():
167             entry.ResetForPack()
168
169     def Pack(self, offset):
170         """Pack all entries into the section"""
171         self._PackEntries()
172         return Entry.Pack(self, offset)
173
174     def _PackEntries(self):
175         """Pack all entries into the image"""
176         offset = self._skip_at_start
177         for entry in self._entries.values():
178             offset = entry.Pack(offset)
179         self.size = self.CheckSize()
180
181     def _ExpandEntries(self):
182         """Expand any entries that are permitted to"""
183         exp_entry = None
184         for entry in self._entries.values():
185             if exp_entry:
186                 exp_entry.ExpandToLimit(entry.offset)
187                 exp_entry = None
188             if entry.expand_size:
189                 exp_entry = entry
190         if exp_entry:
191             exp_entry.ExpandToLimit(self.size)
192
193     def _SortEntries(self):
194         """Sort entries by offset"""
195         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
196         self._entries.clear()
197         for entry in entries:
198             self._entries[entry._node.name] = entry
199
200     def CheckEntries(self):
201         """Check that entries do not overlap or extend outside the image"""
202         if self._sort:
203             self._SortEntries()
204         self._ExpandEntries()
205         offset = 0
206         prev_name = 'None'
207         for entry in self._entries.values():
208             entry.CheckOffset()
209             if (entry.offset < self._skip_at_start or
210                     entry.offset + entry.size > self._skip_at_start +
211                     self.size):
212                 entry.Raise("Offset %#x (%d) is outside the section starting "
213                             "at %#x (%d)" %
214                             (entry.offset, entry.offset, self._skip_at_start,
215                              self._skip_at_start))
216             if entry.offset < offset:
217                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
218                             "ending at %#x (%d)" %
219                             (entry.offset, entry.offset, prev_name, offset, offset))
220             offset = entry.offset + entry.size
221             prev_name = entry.GetPath()
222
223     def WriteSymbols(self, section):
224         """Write symbol values into binary files for access at run time"""
225         for entry in self._entries.values():
226             entry.WriteSymbols(self)
227
228     def SetCalculatedProperties(self):
229         Entry.SetCalculatedProperties(self)
230         for entry in self._entries.values():
231             entry.SetCalculatedProperties()
232
233     def SetImagePos(self, image_pos):
234         Entry.SetImagePos(self, image_pos)
235         for entry in self._entries.values():
236             entry.SetImagePos(image_pos + self.offset)
237
238     def ProcessContents(self):
239         sizes_ok_base = super(Entry_section, self).ProcessContents()
240         sizes_ok = True
241         for entry in self._entries.values():
242             if not entry.ProcessContents():
243                 sizes_ok = False
244         return sizes_ok and sizes_ok_base
245
246     def CheckOffset(self):
247         self.CheckEntries()
248
249     def WriteMap(self, fd, indent):
250         """Write a map of the section to a .map file
251
252         Args:
253             fd: File to write the map to
254         """
255         Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
256                            self.size, self.image_pos)
257         for entry in self._entries.values():
258             entry.WriteMap(fd, indent + 1)
259
260     def GetEntries(self):
261         return self._entries
262
263     def GetContentsByPhandle(self, phandle, source_entry):
264         """Get the data contents of an entry specified by a phandle
265
266         This uses a phandle to look up a node and and find the entry
267         associated with it. Then it returnst he contents of that entry.
268
269         Args:
270             phandle: Phandle to look up (integer)
271             source_entry: Entry containing that phandle (used for error
272                 reporting)
273
274         Returns:
275             data from associated entry (as a string), or None if not found
276         """
277         node = self._node.GetFdt().LookupPhandle(phandle)
278         if not node:
279             source_entry.Raise("Cannot find node for phandle %d" % phandle)
280         for entry in self._entries.values():
281             if entry._node == node:
282                 return entry.GetData()
283         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
284
285     def LookupSymbol(self, sym_name, optional, msg):
286         """Look up a symbol in an ELF file
287
288         Looks up a symbol in an ELF file. Only entry types which come from an
289         ELF image can be used by this function.
290
291         At present the only entry property supported is offset.
292
293         Args:
294             sym_name: Symbol name in the ELF file to look up in the format
295                 _binman_<entry>_prop_<property> where <entry> is the name of
296                 the entry and <property> is the property to find (e.g.
297                 _binman_u_boot_prop_offset). As a special case, you can append
298                 _any to <entry> to have it search for any matching entry. E.g.
299                 _binman_u_boot_any_prop_offset will match entries called u-boot,
300                 u-boot-img and u-boot-nodtb)
301             optional: True if the symbol is optional. If False this function
302                 will raise if the symbol is not found
303             msg: Message to display if an error occurs
304
305         Returns:
306             Value that should be assigned to that symbol, or None if it was
307                 optional and not found
308
309         Raises:
310             ValueError if the symbol is invalid or not found, or references a
311                 property which is not supported
312         """
313         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
314         if not m:
315             raise ValueError("%s: Symbol '%s' has invalid format" %
316                              (msg, sym_name))
317         entry_name, prop_name = m.groups()
318         entry_name = entry_name.replace('_', '-')
319         entry = self._entries.get(entry_name)
320         if not entry:
321             if entry_name.endswith('-any'):
322                 root = entry_name[:-4]
323                 for name in self._entries:
324                     if name.startswith(root):
325                         rest = name[len(root):]
326                         if rest in ['', '-img', '-nodtb']:
327                             entry = self._entries[name]
328         if not entry:
329             err = ("%s: Entry '%s' not found in list (%s)" %
330                    (msg, entry_name, ','.join(self._entries.keys())))
331             if optional:
332                 print('Warning: %s' % err, file=sys.stderr)
333                 return None
334             raise ValueError(err)
335         if prop_name == 'offset':
336             return entry.offset
337         elif prop_name == 'image_pos':
338             return entry.image_pos
339         else:
340             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
341
342     def GetRootSkipAtStart(self):
343         """Get the skip-at-start value for the top-level section
344
345         This is used to find out the starting offset for root section that
346         contains this section. If this is a top-level section then it returns
347         the skip-at-start offset for this section.
348
349         This is used to get the absolute position of section within the image.
350
351         Returns:
352             Integer skip-at-start value for the root section containing this
353                 section
354         """
355         if self.section:
356             return self.section.GetRootSkipAtStart()
357         return self._skip_at_start
358
359     def GetStartOffset(self):
360         """Get the start offset for this section
361
362         Returns:
363             The first available offset in this section (typically 0)
364         """
365         return self._skip_at_start
366
367     def GetImageSize(self):
368         """Get the size of the image containing this section
369
370         Returns:
371             Image size as an integer number of bytes, which may be None if the
372                 image size is dynamic and its sections have not yet been packed
373         """
374         return self.image.size
375
376     def FindEntryType(self, etype):
377         """Find an entry type in the section
378
379         Args:
380             etype: Entry type to find
381         Returns:
382             entry matching that type, or None if not found
383         """
384         for entry in self._entries.values():
385             if entry.etype == etype:
386                 return entry
387         return None
388
389     def GetEntryContents(self):
390         """Call ObtainContents() for the section
391         """
392         todo = self._entries.values()
393         for passnum in range(3):
394             next_todo = []
395             for entry in todo:
396                 if not entry.ObtainContents():
397                     next_todo.append(entry)
398             todo = next_todo
399             if not todo:
400                 break
401         if todo:
402             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
403                        todo)
404         return True
405
406     def _SetEntryOffsetSize(self, name, offset, size):
407         """Set the offset and size of an entry
408
409         Args:
410             name: Entry name to update
411             offset: New offset, or None to leave alone
412             size: New size, or None to leave alone
413         """
414         entry = self._entries.get(name)
415         if not entry:
416             self._Raise("Unable to set offset/size for unknown entry '%s'" %
417                         name)
418         entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
419                             size)
420
421     def GetEntryOffsets(self):
422         """Handle entries that want to set the offset/size of other entries
423
424         This calls each entry's GetOffsets() method. If it returns a list
425         of entries to update, it updates them.
426         """
427         for entry in self._entries.values():
428             offset_dict = entry.GetOffsets()
429             for name, info in offset_dict.items():
430                 self._SetEntryOffsetSize(name, *info)
431
432
433     def CheckSize(self):
434         """Check that the image contents does not exceed its size, etc."""
435         contents_size = 0
436         for entry in self._entries.values():
437             contents_size = max(contents_size, entry.offset + entry.size)
438
439         contents_size -= self._skip_at_start
440
441         size = self.size
442         if not size:
443             size = self.pad_before + contents_size + self.pad_after
444             size = tools.Align(size, self.align_size)
445
446         if self.size and contents_size > self.size:
447             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
448                         (contents_size, contents_size, self.size, self.size))
449         if not self.size:
450             self.size = size
451         if self.size != tools.Align(self.size, self.align_size):
452             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
453                         (self.size, self.size, self.align_size,
454                          self.align_size))
455         return size
456
457     def ListEntries(self, entries, indent):
458         """List the files in the section"""
459         Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
460                            self.image_pos, None, self.offset, self)
461         for entry in self._entries.values():
462             entry.ListEntries(entries, indent + 1)