f29784c1bbfd53d7fd3c8e180cd222ff125787b2
[oweals/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 GetFdts(self):
99         fdts = {}
100         for entry in self._entries.values():
101             fdts.update(entry.GetFdts())
102         return fdts
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         self.Detail('GetData: %d entries, total size %#x' %
153                     (len(self._entries), len(section_data)))
154         return section_data
155
156     def GetOffsets(self):
157         """Handle entries that want to set the offset/size of other entries
158
159         This calls each entry's GetOffsets() method. If it returns a list
160         of entries to update, it updates them.
161         """
162         self.GetEntryOffsets()
163         return {}
164
165     def ResetForPack(self):
166         """Reset offset/size fields so that packing can be done again"""
167         Entry.ResetForPack(self)
168         for entry in self._entries.values():
169             entry.ResetForPack()
170
171     def Pack(self, offset):
172         """Pack all entries into the section"""
173         self._PackEntries()
174         return Entry.Pack(self, offset)
175
176     def _PackEntries(self):
177         """Pack all entries into the image"""
178         offset = self._skip_at_start
179         for entry in self._entries.values():
180             offset = entry.Pack(offset)
181         self.size = self.CheckSize()
182
183     def _ExpandEntries(self):
184         """Expand any entries that are permitted to"""
185         exp_entry = None
186         for entry in self._entries.values():
187             if exp_entry:
188                 exp_entry.ExpandToLimit(entry.offset)
189                 exp_entry = None
190             if entry.expand_size:
191                 exp_entry = entry
192         if exp_entry:
193             exp_entry.ExpandToLimit(self.size)
194
195     def _SortEntries(self):
196         """Sort entries by offset"""
197         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
198         self._entries.clear()
199         for entry in entries:
200             self._entries[entry._node.name] = entry
201
202     def CheckEntries(self):
203         """Check that entries do not overlap or extend outside the image"""
204         if self._sort:
205             self._SortEntries()
206         self._ExpandEntries()
207         offset = 0
208         prev_name = 'None'
209         for entry in self._entries.values():
210             entry.CheckOffset()
211             if (entry.offset < self._skip_at_start or
212                     entry.offset + entry.size > self._skip_at_start +
213                     self.size):
214                 entry.Raise("Offset %#x (%d) is outside the section starting "
215                             "at %#x (%d)" %
216                             (entry.offset, entry.offset, self._skip_at_start,
217                              self._skip_at_start))
218             if entry.offset < offset:
219                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
220                             "ending at %#x (%d)" %
221                             (entry.offset, entry.offset, prev_name, offset, offset))
222             offset = entry.offset + entry.size
223             prev_name = entry.GetPath()
224
225     def WriteSymbols(self, section):
226         """Write symbol values into binary files for access at run time"""
227         for entry in self._entries.values():
228             entry.WriteSymbols(self)
229
230     def SetCalculatedProperties(self):
231         Entry.SetCalculatedProperties(self)
232         for entry in self._entries.values():
233             entry.SetCalculatedProperties()
234
235     def SetImagePos(self, image_pos):
236         Entry.SetImagePos(self, image_pos)
237         for entry in self._entries.values():
238             entry.SetImagePos(image_pos + self.offset)
239
240     def ProcessContents(self):
241         sizes_ok_base = super(Entry_section, self).ProcessContents()
242         sizes_ok = True
243         for entry in self._entries.values():
244             if not entry.ProcessContents():
245                 sizes_ok = False
246         return sizes_ok and sizes_ok_base
247
248     def CheckOffset(self):
249         self.CheckEntries()
250
251     def WriteMap(self, fd, indent):
252         """Write a map of the section to a .map file
253
254         Args:
255             fd: File to write the map to
256         """
257         Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
258                            self.size, self.image_pos)
259         for entry in self._entries.values():
260             entry.WriteMap(fd, indent + 1)
261
262     def GetEntries(self):
263         return self._entries
264
265     def GetContentsByPhandle(self, phandle, source_entry):
266         """Get the data contents of an entry specified by a phandle
267
268         This uses a phandle to look up a node and and find the entry
269         associated with it. Then it returnst he contents of that entry.
270
271         Args:
272             phandle: Phandle to look up (integer)
273             source_entry: Entry containing that phandle (used for error
274                 reporting)
275
276         Returns:
277             data from associated entry (as a string), or None if not found
278         """
279         node = self._node.GetFdt().LookupPhandle(phandle)
280         if not node:
281             source_entry.Raise("Cannot find node for phandle %d" % phandle)
282         for entry in self._entries.values():
283             if entry._node == node:
284                 return entry.GetData()
285         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
286
287     def LookupSymbol(self, sym_name, optional, msg):
288         """Look up a symbol in an ELF file
289
290         Looks up a symbol in an ELF file. Only entry types which come from an
291         ELF image can be used by this function.
292
293         At present the only entry property supported is offset.
294
295         Args:
296             sym_name: Symbol name in the ELF file to look up in the format
297                 _binman_<entry>_prop_<property> where <entry> is the name of
298                 the entry and <property> is the property to find (e.g.
299                 _binman_u_boot_prop_offset). As a special case, you can append
300                 _any to <entry> to have it search for any matching entry. E.g.
301                 _binman_u_boot_any_prop_offset will match entries called u-boot,
302                 u-boot-img and u-boot-nodtb)
303             optional: True if the symbol is optional. If False this function
304                 will raise if the symbol is not found
305             msg: Message to display if an error occurs
306
307         Returns:
308             Value that should be assigned to that symbol, or None if it was
309                 optional and not found
310
311         Raises:
312             ValueError if the symbol is invalid or not found, or references a
313                 property which is not supported
314         """
315         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
316         if not m:
317             raise ValueError("%s: Symbol '%s' has invalid format" %
318                              (msg, sym_name))
319         entry_name, prop_name = m.groups()
320         entry_name = entry_name.replace('_', '-')
321         entry = self._entries.get(entry_name)
322         if not entry:
323             if entry_name.endswith('-any'):
324                 root = entry_name[:-4]
325                 for name in self._entries:
326                     if name.startswith(root):
327                         rest = name[len(root):]
328                         if rest in ['', '-img', '-nodtb']:
329                             entry = self._entries[name]
330         if not entry:
331             err = ("%s: Entry '%s' not found in list (%s)" %
332                    (msg, entry_name, ','.join(self._entries.keys())))
333             if optional:
334                 print('Warning: %s' % err, file=sys.stderr)
335                 return None
336             raise ValueError(err)
337         if prop_name == 'offset':
338             return entry.offset
339         elif prop_name == 'image_pos':
340             return entry.image_pos
341         else:
342             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
343
344     def GetRootSkipAtStart(self):
345         """Get the skip-at-start value for the top-level section
346
347         This is used to find out the starting offset for root section that
348         contains this section. If this is a top-level section then it returns
349         the skip-at-start offset for this section.
350
351         This is used to get the absolute position of section within the image.
352
353         Returns:
354             Integer skip-at-start value for the root section containing this
355                 section
356         """
357         if self.section:
358             return self.section.GetRootSkipAtStart()
359         return self._skip_at_start
360
361     def GetStartOffset(self):
362         """Get the start offset for this section
363
364         Returns:
365             The first available offset in this section (typically 0)
366         """
367         return self._skip_at_start
368
369     def GetImageSize(self):
370         """Get the size of the image containing this section
371
372         Returns:
373             Image size as an integer number of bytes, which may be None if the
374                 image size is dynamic and its sections have not yet been packed
375         """
376         return self.image.size
377
378     def FindEntryType(self, etype):
379         """Find an entry type in the section
380
381         Args:
382             etype: Entry type to find
383         Returns:
384             entry matching that type, or None if not found
385         """
386         for entry in self._entries.values():
387             if entry.etype == etype:
388                 return entry
389         return None
390
391     def GetEntryContents(self):
392         """Call ObtainContents() for the section
393         """
394         todo = self._entries.values()
395         for passnum in range(3):
396             next_todo = []
397             for entry in todo:
398                 if not entry.ObtainContents():
399                     next_todo.append(entry)
400             todo = next_todo
401             if not todo:
402                 break
403         if todo:
404             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
405                        todo)
406         return True
407
408     def _SetEntryOffsetSize(self, name, offset, size):
409         """Set the offset and size of an entry
410
411         Args:
412             name: Entry name to update
413             offset: New offset, or None to leave alone
414             size: New size, or None to leave alone
415         """
416         entry = self._entries.get(name)
417         if not entry:
418             self._Raise("Unable to set offset/size for unknown entry '%s'" %
419                         name)
420         entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
421                             size)
422
423     def GetEntryOffsets(self):
424         """Handle entries that want to set the offset/size of other entries
425
426         This calls each entry's GetOffsets() method. If it returns a list
427         of entries to update, it updates them.
428         """
429         for entry in self._entries.values():
430             offset_dict = entry.GetOffsets()
431             for name, info in offset_dict.items():
432                 self._SetEntryOffsetSize(name, *info)
433
434
435     def CheckSize(self):
436         """Check that the image contents does not exceed its size, etc."""
437         contents_size = 0
438         for entry in self._entries.values():
439             contents_size = max(contents_size, entry.offset + entry.size)
440
441         contents_size -= self._skip_at_start
442
443         size = self.size
444         if not size:
445             size = self.pad_before + contents_size + self.pad_after
446             size = tools.Align(size, self.align_size)
447
448         if self.size and contents_size > self.size:
449             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
450                         (contents_size, contents_size, self.size, self.size))
451         if not self.size:
452             self.size = size
453         if self.size != tools.Align(self.size, self.align_size):
454             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
455                         (self.size, self.size, self.align_size,
456                          self.align_size))
457         return size
458
459     def ListEntries(self, entries, indent):
460         """List the files in the section"""
461         Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
462                            self.image_pos, None, self.offset, self)
463         for entry in self._entries.values():
464             entry.ListEntries(entries, indent + 1)