binman: Allow support for writing a size symbol to binaries
[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         if prop_name == 'size':
348             return entry.size
349         else:
350             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
351
352     def GetRootSkipAtStart(self):
353         """Get the skip-at-start value for the top-level section
354
355         This is used to find out the starting offset for root section that
356         contains this section. If this is a top-level section then it returns
357         the skip-at-start offset for this section.
358
359         This is used to get the absolute position of section within the image.
360
361         Returns:
362             Integer skip-at-start value for the root section containing this
363                 section
364         """
365         if self.section:
366             return self.section.GetRootSkipAtStart()
367         return self._skip_at_start
368
369     def GetStartOffset(self):
370         """Get the start offset for this section
371
372         Returns:
373             The first available offset in this section (typically 0)
374         """
375         return self._skip_at_start
376
377     def GetImageSize(self):
378         """Get the size of the image containing this section
379
380         Returns:
381             Image size as an integer number of bytes, which may be None if the
382                 image size is dynamic and its sections have not yet been packed
383         """
384         return self.GetImage().size
385
386     def FindEntryType(self, etype):
387         """Find an entry type in the section
388
389         Args:
390             etype: Entry type to find
391         Returns:
392             entry matching that type, or None if not found
393         """
394         for entry in self._entries.values():
395             if entry.etype == etype:
396                 return entry
397         return None
398
399     def GetEntryContents(self):
400         """Call ObtainContents() for the section
401         """
402         todo = self._entries.values()
403         for passnum in range(3):
404             next_todo = []
405             for entry in todo:
406                 if not entry.ObtainContents():
407                     next_todo.append(entry)
408             todo = next_todo
409             if not todo:
410                 break
411         if todo:
412             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
413                        todo)
414         return True
415
416     def _SetEntryOffsetSize(self, name, offset, size):
417         """Set the offset and size of an entry
418
419         Args:
420             name: Entry name to update
421             offset: New offset, or None to leave alone
422             size: New size, or None to leave alone
423         """
424         entry = self._entries.get(name)
425         if not entry:
426             self._Raise("Unable to set offset/size for unknown entry '%s'" %
427                         name)
428         entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
429                             size)
430
431     def GetEntryOffsets(self):
432         """Handle entries that want to set the offset/size of other entries
433
434         This calls each entry's GetOffsets() method. If it returns a list
435         of entries to update, it updates them.
436         """
437         for entry in self._entries.values():
438             offset_dict = entry.GetOffsets()
439             for name, info in offset_dict.items():
440                 self._SetEntryOffsetSize(name, *info)
441
442
443     def CheckSize(self):
444         """Check that the image contents does not exceed its size, etc."""
445         contents_size = 0
446         for entry in self._entries.values():
447             contents_size = max(contents_size, entry.offset + entry.size)
448
449         contents_size -= self._skip_at_start
450
451         size = self.size
452         if not size:
453             size = self.pad_before + contents_size + self.pad_after
454             size = tools.Align(size, self.align_size)
455
456         if self.size and contents_size > self.size:
457             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
458                         (contents_size, contents_size, self.size, self.size))
459         if not self.size:
460             self.size = size
461         if self.size != tools.Align(self.size, self.align_size):
462             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
463                         (self.size, self.size, self.align_size,
464                          self.align_size))
465         return size
466
467     def ListEntries(self, entries, indent):
468         """List the files in the section"""
469         Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
470                            self.image_pos, None, self.offset, self)
471         for entry in self._entries.values():
472             entry.ListEntries(entries, indent + 1)
473
474     def LoadData(self, decomp=True):
475         for entry in self._entries.values():
476             entry.LoadData(decomp)
477         self.Detail('Loaded data')
478
479     def GetImage(self):
480         """Get the image containing this section
481
482         Note that a top-level section is actually an Image, so this function may
483         return self.
484
485         Returns:
486             Image object containing this section
487         """
488         if not self.section:
489             return self
490         return self.section.GetImage()
491
492     def GetSort(self):
493         """Check if the entries in this section will be sorted
494
495         Returns:
496             True if to be sorted, False if entries will be left in the order
497                 they appear in the device tree
498         """
499         return self._sort
500
501     def ReadData(self, decomp=True):
502         tout.Info("ReadData path='%s'" % self.GetPath())
503         parent_data = self.section.ReadData(True)
504         tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
505                   (self.GetPath(), self.offset, self.offset + self.size,
506                    self.size))
507         data = parent_data[self.offset:self.offset + self.size]
508         return data
509
510     def ReadChildData(self, child, decomp=True):
511         tout.Debug("ReadChildData for child '%s'" % child.GetPath())
512         parent_data = self.ReadData(True)
513         offset = child.offset - self._skip_at_start
514         tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
515                    (child.GetPath(), child.offset, self._skip_at_start, offset))
516         data = parent_data[offset:offset + child.size]
517         if decomp:
518             indata = data
519             data = tools.Decompress(indata, child.compress)
520             if child.uncomp_size:
521                 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
522                             (child.GetPath(), len(indata), child.compress,
523                             len(data)))
524         return data
525
526     def WriteChildData(self, child):
527         return True