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