binman: Avoid needing the section size in advance
[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 import tout
21
22
23 class Entry_section(Entry):
24     """Entry that contains other entries
25
26     Properties / Entry arguments: (see binman README for more information)
27         pad-byte: Pad byte to use when padding
28         sort-by-offset: True if entries should be sorted by offset, False if
29             they must be in-order in the device tree description
30         end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
31         skip-at-start: Number of bytes before the first entry starts. These
32             effectively adjust the starting offset of entries. For example,
33             if this is 16, then the first entry would start at 16. An entry
34             with offset = 20 would in fact be written at offset 4 in the image
35             file, since the first 16 bytes are skipped when writing.
36         name-prefix: Adds a prefix to the name of every entry in the section
37             when writing out the map
38
39     Since a section is also an entry, it inherits all the properies of entries
40     too.
41
42     A section is an entry which can contain other entries, thus allowing
43     hierarchical images to be created. See 'Sections and hierarchical images'
44     in the binman README for more information.
45     """
46     def __init__(self, section, etype, node, test=False):
47         if not test:
48             Entry.__init__(self, section, etype, node)
49         self._entries = OrderedDict()
50         self._pad_byte = 0
51         self._sort = False
52         self._skip_at_start = None
53         self._end_4gb = False
54
55     def ReadNode(self):
56         """Read properties from the image node"""
57         Entry.ReadNode(self)
58         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
59         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
60         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
61         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
62         if self._end_4gb:
63             if not self.size:
64                 self.Raise("Section size must be provided when using end-at-4gb")
65             if self._skip_at_start is not None:
66                 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
67             else:
68                 self._skip_at_start = 0x100000000 - self.size
69         else:
70             if self._skip_at_start is None:
71                 self._skip_at_start = 0
72         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
73         filename = fdt_util.GetString(self._node, 'filename')
74         if filename:
75             self._filename = filename
76
77         self._ReadEntries()
78
79     def _ReadEntries(self):
80         for node in self._node.subnodes:
81             if node.name == 'hash':
82                 continue
83             entry = Entry.Create(self, node)
84             entry.ReadNode()
85             entry.SetPrefix(self._name_prefix)
86             self._entries[node.name] = entry
87
88     def _Raise(self, msg):
89         """Raises an error for this section
90
91         Args:
92             msg: Error message to use in the raise string
93         Raises:
94             ValueError()
95         """
96         raise ValueError("Section '%s': %s" % (self._node.path, msg))
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 = b''
146
147         for entry in self._entries.values():
148             data = entry.GetData()
149             base = self.pad_before + (entry.offset or 0) - self._skip_at_start
150             pad = base - len(section_data)
151             if pad > 0:
152                 section_data += tools.GetBytes(self._pad_byte, pad)
153             section_data += data
154         if self.size:
155             pad = self.size - len(section_data)
156             if pad > 0:
157                 section_data += tools.GetBytes(self._pad_byte, pad)
158         self.Detail('GetData: %d entries, total size %#x' %
159                     (len(self._entries), len(section_data)))
160         return section_data
161
162     def GetOffsets(self):
163         """Handle entries that want to set the offset/size of other entries
164
165         This calls each entry's GetOffsets() method. If it returns a list
166         of entries to update, it updates them.
167         """
168         self.GetEntryOffsets()
169         return {}
170
171     def ResetForPack(self):
172         """Reset offset/size fields so that packing can be done again"""
173         Entry.ResetForPack(self)
174         for entry in self._entries.values():
175             entry.ResetForPack()
176
177     def Pack(self, offset):
178         """Pack all entries into the section"""
179         self._PackEntries()
180         return Entry.Pack(self, offset)
181
182     def _PackEntries(self):
183         """Pack all entries into the image"""
184         offset = self._skip_at_start
185         for entry in self._entries.values():
186             offset = entry.Pack(offset)
187         self.size = self.CheckSize()
188
189     def _ExpandEntries(self):
190         """Expand any entries that are permitted to"""
191         exp_entry = None
192         for entry in self._entries.values():
193             if exp_entry:
194                 exp_entry.ExpandToLimit(entry.offset)
195                 exp_entry = None
196             if entry.expand_size:
197                 exp_entry = entry
198         if exp_entry:
199             exp_entry.ExpandToLimit(self.size)
200
201     def _SortEntries(self):
202         """Sort entries by offset"""
203         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
204         self._entries.clear()
205         for entry in entries:
206             self._entries[entry._node.name] = entry
207
208     def CheckEntries(self):
209         """Check that entries do not overlap or extend outside the image"""
210         if self._sort:
211             self._SortEntries()
212         self._ExpandEntries()
213         offset = 0
214         prev_name = 'None'
215         for entry in self._entries.values():
216             entry.CheckOffset()
217             if (entry.offset < self._skip_at_start or
218                     entry.offset + entry.size > self._skip_at_start +
219                     self.size):
220                 entry.Raise("Offset %#x (%d) is outside the section starting "
221                             "at %#x (%d)" %
222                             (entry.offset, entry.offset, self._skip_at_start,
223                              self._skip_at_start))
224             if entry.offset < offset:
225                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
226                             "ending at %#x (%d)" %
227                             (entry.offset, entry.offset, prev_name, offset, offset))
228             offset = entry.offset + entry.size
229             prev_name = entry.GetPath()
230
231     def WriteSymbols(self, section):
232         """Write symbol values into binary files for access at run time"""
233         for entry in self._entries.values():
234             entry.WriteSymbols(self)
235
236     def SetCalculatedProperties(self):
237         Entry.SetCalculatedProperties(self)
238         for entry in self._entries.values():
239             entry.SetCalculatedProperties()
240
241     def SetImagePos(self, image_pos):
242         Entry.SetImagePos(self, image_pos)
243         for entry in self._entries.values():
244             entry.SetImagePos(image_pos + self.offset)
245
246     def ProcessContents(self):
247         sizes_ok_base = super(Entry_section, self).ProcessContents()
248         sizes_ok = True
249         for entry in self._entries.values():
250             if not entry.ProcessContents():
251                 sizes_ok = False
252         return sizes_ok and sizes_ok_base
253
254     def CheckOffset(self):
255         self.CheckEntries()
256
257     def WriteMap(self, fd, indent):
258         """Write a map of the section to a .map file
259
260         Args:
261             fd: File to write the map to
262         """
263         Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
264                            self.size, self.image_pos)
265         for entry in self._entries.values():
266             entry.WriteMap(fd, indent + 1)
267
268     def GetEntries(self):
269         return self._entries
270
271     def GetContentsByPhandle(self, phandle, source_entry):
272         """Get the data contents of an entry specified by a phandle
273
274         This uses a phandle to look up a node and and find the entry
275         associated with it. Then it returnst he contents of that entry.
276
277         Args:
278             phandle: Phandle to look up (integer)
279             source_entry: Entry containing that phandle (used for error
280                 reporting)
281
282         Returns:
283             data from associated entry (as a string), or None if not found
284         """
285         node = self._node.GetFdt().LookupPhandle(phandle)
286         if not node:
287             source_entry.Raise("Cannot find node for phandle %d" % phandle)
288         for entry in self._entries.values():
289             if entry._node == node:
290                 return entry.GetData()
291         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
292
293     def LookupSymbol(self, sym_name, optional, msg):
294         """Look up a symbol in an ELF file
295
296         Looks up a symbol in an ELF file. Only entry types which come from an
297         ELF image can be used by this function.
298
299         At present the only entry property supported is offset.
300
301         Args:
302             sym_name: Symbol name in the ELF file to look up in the format
303                 _binman_<entry>_prop_<property> where <entry> is the name of
304                 the entry and <property> is the property to find (e.g.
305                 _binman_u_boot_prop_offset). As a special case, you can append
306                 _any to <entry> to have it search for any matching entry. E.g.
307                 _binman_u_boot_any_prop_offset will match entries called u-boot,
308                 u-boot-img and u-boot-nodtb)
309             optional: True if the symbol is optional. If False this function
310                 will raise if the symbol is not found
311             msg: Message to display if an error occurs
312
313         Returns:
314             Value that should be assigned to that symbol, or None if it was
315                 optional and not found
316
317         Raises:
318             ValueError if the symbol is invalid or not found, or references a
319                 property which is not supported
320         """
321         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
322         if not m:
323             raise ValueError("%s: Symbol '%s' has invalid format" %
324                              (msg, sym_name))
325         entry_name, prop_name = m.groups()
326         entry_name = entry_name.replace('_', '-')
327         entry = self._entries.get(entry_name)
328         if not entry:
329             if entry_name.endswith('-any'):
330                 root = entry_name[:-4]
331                 for name in self._entries:
332                     if name.startswith(root):
333                         rest = name[len(root):]
334                         if rest in ['', '-img', '-nodtb']:
335                             entry = self._entries[name]
336         if not entry:
337             err = ("%s: Entry '%s' not found in list (%s)" %
338                    (msg, entry_name, ','.join(self._entries.keys())))
339             if optional:
340                 print('Warning: %s' % err, file=sys.stderr)
341                 return None
342             raise ValueError(err)
343         if prop_name == 'offset':
344             return entry.offset
345         elif prop_name == 'image_pos':
346             return entry.image_pos
347         else:
348             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
349
350     def GetRootSkipAtStart(self):
351         """Get the skip-at-start value for the top-level section
352
353         This is used to find out the starting offset for root section that
354         contains this section. If this is a top-level section then it returns
355         the skip-at-start offset for this section.
356
357         This is used to get the absolute position of section within the image.
358
359         Returns:
360             Integer skip-at-start value for the root section containing this
361                 section
362         """
363         if self.section:
364             return self.section.GetRootSkipAtStart()
365         return self._skip_at_start
366
367     def GetStartOffset(self):
368         """Get the start offset for this section
369
370         Returns:
371             The first available offset in this section (typically 0)
372         """
373         return self._skip_at_start
374
375     def GetImageSize(self):
376         """Get the size of the image containing this section
377
378         Returns:
379             Image size as an integer number of bytes, which may be None if the
380                 image size is dynamic and its sections have not yet been packed
381         """
382         return self.GetImage().size
383
384     def FindEntryType(self, etype):
385         """Find an entry type in the section
386
387         Args:
388             etype: Entry type to find
389         Returns:
390             entry matching that type, or None if not found
391         """
392         for entry in self._entries.values():
393             if entry.etype == etype:
394                 return entry
395         return None
396
397     def GetEntryContents(self):
398         """Call ObtainContents() for the section
399         """
400         todo = self._entries.values()
401         for passnum in range(3):
402             next_todo = []
403             for entry in todo:
404                 if not entry.ObtainContents():
405                     next_todo.append(entry)
406             todo = next_todo
407             if not todo:
408                 break
409         if todo:
410             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
411                        todo)
412         return True
413
414     def _SetEntryOffsetSize(self, name, offset, size):
415         """Set the offset and size of an entry
416
417         Args:
418             name: Entry name to update
419             offset: New offset, or None to leave alone
420             size: New size, or None to leave alone
421         """
422         entry = self._entries.get(name)
423         if not entry:
424             self._Raise("Unable to set offset/size for unknown entry '%s'" %
425                         name)
426         entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
427                             size)
428
429     def GetEntryOffsets(self):
430         """Handle entries that want to set the offset/size of other entries
431
432         This calls each entry's GetOffsets() method. If it returns a list
433         of entries to update, it updates them.
434         """
435         for entry in self._entries.values():
436             offset_dict = entry.GetOffsets()
437             for name, info in offset_dict.items():
438                 self._SetEntryOffsetSize(name, *info)
439
440
441     def CheckSize(self):
442         """Check that the image contents does not exceed its size, etc."""
443         contents_size = 0
444         for entry in self._entries.values():
445             contents_size = max(contents_size, entry.offset + entry.size)
446
447         contents_size -= self._skip_at_start
448
449         size = self.size
450         if not size:
451             size = self.pad_before + contents_size + self.pad_after
452             size = tools.Align(size, self.align_size)
453
454         if self.size and contents_size > self.size:
455             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
456                         (contents_size, contents_size, self.size, self.size))
457         if not self.size:
458             self.size = size
459         if self.size != tools.Align(self.size, self.align_size):
460             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
461                         (self.size, self.size, self.align_size,
462                          self.align_size))
463         return size
464
465     def ListEntries(self, entries, indent):
466         """List the files in the section"""
467         Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
468                            self.image_pos, None, self.offset, self)
469         for entry in self._entries.values():
470             entry.ListEntries(entries, indent + 1)
471
472     def LoadData(self, decomp=True):
473         for entry in self._entries.values():
474             entry.LoadData(decomp)
475         self.Detail('Loaded data')
476
477     def GetImage(self):
478         """Get the image containing this section
479
480         Note that a top-level section is actually an Image, so this function may
481         return self.
482
483         Returns:
484             Image object containing this section
485         """
486         if not self.section:
487             return self
488         return self.section.GetImage()
489
490     def GetSort(self):
491         """Check if the entries in this section will be sorted
492
493         Returns:
494             True if to be sorted, False if entries will be left in the order
495                 they appear in the device tree
496         """
497         return self._sort
498
499     def ReadData(self, decomp=True):
500         tout.Info("ReadData path='%s'" % self.GetPath())
501         parent_data = self.section.ReadData(True)
502         tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
503                   (self.GetPath(), self.offset, self.offset + self.size,
504                    self.size))
505         data = parent_data[self.offset:self.offset + self.size]
506         return data
507
508     def ReadChildData(self, child, decomp=True):
509         tout.Debug("ReadChildData for child '%s'" % child.GetPath())
510         parent_data = self.ReadData(True)
511         offset = child.offset - self._skip_at_start
512         tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
513                    (child.GetPath(), child.offset, self._skip_at_start, offset))
514         data = parent_data[offset:offset + child.size]
515         if decomp:
516             indata = data
517             data = tools.Decompress(indata, child.compress)
518             if child.uncomp_size:
519                 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
520                             (child.GetPath(), len(indata), child.compress,
521                             len(data)))
522         return data
523
524     def WriteChildData(self, child):
525         return True