binman: Set section contents in GetData()
[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 collections import OrderedDict
12 import re
13 import sys
14
15 from binman.entry import Entry
16 from dtoc import fdt_util
17 from patman import tools
18 from patman import tout
19 from patman.tools import ToHexSize
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     Properties:
39         allow_missing: True if this section permits external blobs to be
40             missing their contents. The second will produce an image but of
41             course it will not work.
42
43     Since a section is also an entry, it inherits all the properies of entries
44     too.
45
46     A section is an entry which can contain other entries, thus allowing
47     hierarchical images to be created. See 'Sections and hierarchical images'
48     in the binman README for more information.
49     """
50     def __init__(self, section, etype, node, test=False):
51         if not test:
52             super().__init__(section, etype, node)
53         self._entries = OrderedDict()
54         self._pad_byte = 0
55         self._sort = False
56         self._skip_at_start = None
57         self._end_4gb = False
58
59     def ReadNode(self):
60         """Read properties from the section node"""
61         super().ReadNode()
62         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
63         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
64         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
65         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
66         if self._end_4gb:
67             if not self.size:
68                 self.Raise("Section size must be provided when using end-at-4gb")
69             if self._skip_at_start is not None:
70                 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
71             else:
72                 self._skip_at_start = 0x100000000 - self.size
73         else:
74             if self._skip_at_start is None:
75                 self._skip_at_start = 0
76         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
77         filename = fdt_util.GetString(self._node, 'filename')
78         if filename:
79             self._filename = filename
80
81         self._ReadEntries()
82
83     def _ReadEntries(self):
84         for node in self._node.subnodes:
85             if node.name.startswith('hash') or node.name.startswith('signature'):
86                 continue
87             entry = Entry.Create(self, node)
88             entry.ReadNode()
89             entry.SetPrefix(self._name_prefix)
90             self._entries[node.name] = entry
91
92     def _Raise(self, msg):
93         """Raises an error for this section
94
95         Args:
96             msg: Error message to use in the raise string
97         Raises:
98             ValueError()
99         """
100         raise ValueError("Section '%s': %s" % (self._node.path, msg))
101
102     def GetFdts(self):
103         fdts = {}
104         for entry in self._entries.values():
105             fdts.update(entry.GetFdts())
106         return fdts
107
108     def ProcessFdt(self, fdt):
109         """Allow entries to adjust the device tree
110
111         Some entries need to adjust the device tree for their purposes. This
112         may involve adding or deleting properties.
113         """
114         todo = self._entries.values()
115         for passnum in range(3):
116             next_todo = []
117             for entry in todo:
118                 if not entry.ProcessFdt(fdt):
119                     next_todo.append(entry)
120             todo = next_todo
121             if not todo:
122                 break
123         if todo:
124             self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
125                        todo)
126         return True
127
128     def ExpandEntries(self):
129         """Expand out any entries which have calculated sub-entries
130
131         Some entries are expanded out at runtime, e.g. 'files', which produces
132         a section containing a list of files. Process these entries so that
133         this information is added to the device tree.
134         """
135         super().ExpandEntries()
136         for entry in self._entries.values():
137             entry.ExpandEntries()
138
139     def AddMissingProperties(self):
140         """Add new properties to the device tree as needed for this entry"""
141         super().AddMissingProperties()
142         for entry in self._entries.values():
143             entry.AddMissingProperties()
144
145     def ObtainContents(self):
146         return self.GetEntryContents()
147
148     def GetPaddedDataForEntry(self, entry):
149         """Get the data for an entry including any padding
150
151         Gets the entry data and uses the section pad-byte value to add padding
152         before and after as defined by the pad-before and pad-after properties.
153         This does not consider alignment.
154
155         Args:
156             entry: Entry to check
157
158         Returns:
159             Contents of the entry along with any pad bytes before and
160             after it (bytes)
161         """
162         pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
163                     else self._pad_byte)
164
165         data = b''
166         # Handle padding before the entry
167         if entry.pad_before:
168             data += tools.GetBytes(self._pad_byte, entry.pad_before)
169
170         # Add in the actual entry data
171         data += entry.GetData()
172
173         # Handle padding after the entry
174         if entry.pad_after:
175             data += tools.GetBytes(self._pad_byte, entry.pad_after)
176
177         if entry.size:
178             data += tools.GetBytes(pad_byte, entry.size - len(data))
179
180         self.Detail('GetPaddedDataForEntry: size %s' % ToHexSize(self.data))
181
182         return data
183
184     def _BuildSectionData(self):
185         """Build the contents of a section
186
187         This places all entries at the right place, dealing with padding before
188         and after entries. It does not do padding for the section itself (the
189         pad-before and pad-after properties in the section items) since that is
190         handled by the parent section.
191
192         Returns:
193             Contents of the section (bytes)
194         """
195         section_data = b''
196
197         for entry in self._entries.values():
198             data = self.GetPaddedDataForEntry(entry)
199             # Handle empty space before the entry
200             pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
201             if pad > 0:
202                 section_data += tools.GetBytes(self._pad_byte, pad)
203
204             # Add in the actual entry data
205             section_data += data
206
207         self.Detail('GetData: %d entries, total size %#x' %
208                     (len(self._entries), len(section_data)))
209         return self.CompressData(section_data)
210
211     def GetPaddedData(self):
212         """Get the data for a section including any padding
213
214         Gets the section data and uses the parent section's pad-byte value to
215         add padding before and after as defined by the pad-before and pad-after
216         properties. If this is a top-level section (i.e. an image), this is the
217         same as GetData(), since padding is not supported.
218
219         This does not consider alignment.
220
221         Returns:
222             Contents of the section along with any pad bytes before and
223             after it (bytes)
224         """
225         section = self.section or self
226         return section.GetPaddedDataForEntry(self)
227
228     def GetData(self):
229         """Get the contents of an entry
230
231         This builds the contents of the section, stores this as the contents of
232         the section and returns it
233
234         Returns:
235             bytes content of the section, made up for all all of its subentries.
236             This excludes any padding. If the section is compressed, the
237             compressed data is returned
238         """
239         data = self._BuildSectionData()
240         self.SetContents(data)
241         return data
242
243     def GetOffsets(self):
244         """Handle entries that want to set the offset/size of other entries
245
246         This calls each entry's GetOffsets() method. If it returns a list
247         of entries to update, it updates them.
248         """
249         self.GetEntryOffsets()
250         return {}
251
252     def ResetForPack(self):
253         """Reset offset/size fields so that packing can be done again"""
254         super().ResetForPack()
255         for entry in self._entries.values():
256             entry.ResetForPack()
257
258     def Pack(self, offset):
259         """Pack all entries into the section"""
260         self._PackEntries()
261         return super().Pack(offset)
262
263     def _PackEntries(self):
264         """Pack all entries into the section"""
265         offset = self._skip_at_start
266         for entry in self._entries.values():
267             offset = entry.Pack(offset)
268         self.size = self.CheckSize()
269
270     def _ExpandEntries(self):
271         """Expand any entries that are permitted to"""
272         exp_entry = None
273         for entry in self._entries.values():
274             if exp_entry:
275                 exp_entry.ExpandToLimit(entry.offset)
276                 exp_entry = None
277             if entry.expand_size:
278                 exp_entry = entry
279         if exp_entry:
280             exp_entry.ExpandToLimit(self.size)
281
282     def _SortEntries(self):
283         """Sort entries by offset"""
284         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
285         self._entries.clear()
286         for entry in entries:
287             self._entries[entry._node.name] = entry
288
289     def CheckEntries(self):
290         """Check that entries do not overlap or extend outside the section"""
291         if self._sort:
292             self._SortEntries()
293         self._ExpandEntries()
294         offset = 0
295         prev_name = 'None'
296         for entry in self._entries.values():
297             entry.CheckOffset()
298             if (entry.offset < self._skip_at_start or
299                     entry.offset + entry.size > self._skip_at_start +
300                     self.size):
301                 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
302                             "section '%s' starting at %#x (%d) "
303                             'of size %#x (%d)' %
304                             (entry.offset, entry.offset, entry.size, entry.size,
305                              self._node.path, self._skip_at_start,
306                              self._skip_at_start, self.size, self.size))
307             if entry.offset < offset and entry.size:
308                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
309                             "ending at %#x (%d)" %
310                             (entry.offset, entry.offset, prev_name, offset, offset))
311             offset = entry.offset + entry.size
312             prev_name = entry.GetPath()
313
314     def WriteSymbols(self, section):
315         """Write symbol values into binary files for access at run time"""
316         for entry in self._entries.values():
317             entry.WriteSymbols(self)
318
319     def SetCalculatedProperties(self):
320         super().SetCalculatedProperties()
321         for entry in self._entries.values():
322             entry.SetCalculatedProperties()
323
324     def SetImagePos(self, image_pos):
325         super().SetImagePos(image_pos)
326         for entry in self._entries.values():
327             entry.SetImagePos(image_pos + self.offset)
328
329     def ProcessContents(self):
330         sizes_ok_base = super(Entry_section, self).ProcessContents()
331         sizes_ok = True
332         for entry in self._entries.values():
333             if not entry.ProcessContents():
334                 sizes_ok = False
335         return sizes_ok and sizes_ok_base
336
337     def CheckOffset(self):
338         self.CheckEntries()
339
340     def WriteMap(self, fd, indent):
341         """Write a map of the section to a .map file
342
343         Args:
344             fd: File to write the map to
345         """
346         Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
347                            self.size, self.image_pos)
348         for entry in self._entries.values():
349             entry.WriteMap(fd, indent + 1)
350
351     def GetEntries(self):
352         return self._entries
353
354     def GetContentsByPhandle(self, phandle, source_entry):
355         """Get the data contents of an entry specified by a phandle
356
357         This uses a phandle to look up a node and and find the entry
358         associated with it. Then it returnst he contents of that entry.
359
360         Args:
361             phandle: Phandle to look up (integer)
362             source_entry: Entry containing that phandle (used for error
363                 reporting)
364
365         Returns:
366             data from associated entry (as a string), or None if not found
367         """
368         node = self._node.GetFdt().LookupPhandle(phandle)
369         if not node:
370             source_entry.Raise("Cannot find node for phandle %d" % phandle)
371         for entry in self._entries.values():
372             if entry._node == node:
373                 return entry.GetData()
374         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
375
376     def LookupSymbol(self, sym_name, optional, msg, base_addr):
377         """Look up a symbol in an ELF file
378
379         Looks up a symbol in an ELF file. Only entry types which come from an
380         ELF image can be used by this function.
381
382         At present the only entry properties supported are:
383             offset
384             image_pos - 'base_addr' is added if this is not an end-at-4gb image
385             size
386
387         Args:
388             sym_name: Symbol name in the ELF file to look up in the format
389                 _binman_<entry>_prop_<property> where <entry> is the name of
390                 the entry and <property> is the property to find (e.g.
391                 _binman_u_boot_prop_offset). As a special case, you can append
392                 _any to <entry> to have it search for any matching entry. E.g.
393                 _binman_u_boot_any_prop_offset will match entries called u-boot,
394                 u-boot-img and u-boot-nodtb)
395             optional: True if the symbol is optional. If False this function
396                 will raise if the symbol is not found
397             msg: Message to display if an error occurs
398             base_addr: Base address of image. This is added to the returned
399                 image_pos in most cases so that the returned position indicates
400                 where the targetted entry/binary has actually been loaded. But
401                 if end-at-4gb is used, this is not done, since the binary is
402                 already assumed to be linked to the ROM position and using
403                 execute-in-place (XIP).
404
405         Returns:
406             Value that should be assigned to that symbol, or None if it was
407                 optional and not found
408
409         Raises:
410             ValueError if the symbol is invalid or not found, or references a
411                 property which is not supported
412         """
413         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
414         if not m:
415             raise ValueError("%s: Symbol '%s' has invalid format" %
416                              (msg, sym_name))
417         entry_name, prop_name = m.groups()
418         entry_name = entry_name.replace('_', '-')
419         entry = self._entries.get(entry_name)
420         if not entry:
421             if entry_name.endswith('-any'):
422                 root = entry_name[:-4]
423                 for name in self._entries:
424                     if name.startswith(root):
425                         rest = name[len(root):]
426                         if rest in ['', '-img', '-nodtb']:
427                             entry = self._entries[name]
428         if not entry:
429             err = ("%s: Entry '%s' not found in list (%s)" %
430                    (msg, entry_name, ','.join(self._entries.keys())))
431             if optional:
432                 print('Warning: %s' % err, file=sys.stderr)
433                 return None
434             raise ValueError(err)
435         if prop_name == 'offset':
436             return entry.offset
437         elif prop_name == 'image_pos':
438             value = entry.image_pos
439             if not self.GetImage()._end_4gb:
440                 value += base_addr
441             return value
442         if prop_name == 'size':
443             return entry.size
444         else:
445             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
446
447     def GetRootSkipAtStart(self):
448         """Get the skip-at-start value for the top-level section
449
450         This is used to find out the starting offset for root section that
451         contains this section. If this is a top-level section then it returns
452         the skip-at-start offset for this section.
453
454         This is used to get the absolute position of section within the image.
455
456         Returns:
457             Integer skip-at-start value for the root section containing this
458                 section
459         """
460         if self.section:
461             return self.section.GetRootSkipAtStart()
462         return self._skip_at_start
463
464     def GetStartOffset(self):
465         """Get the start offset for this section
466
467         Returns:
468             The first available offset in this section (typically 0)
469         """
470         return self._skip_at_start
471
472     def GetImageSize(self):
473         """Get the size of the image containing this section
474
475         Returns:
476             Image size as an integer number of bytes, which may be None if the
477                 image size is dynamic and its sections have not yet been packed
478         """
479         return self.GetImage().size
480
481     def FindEntryType(self, etype):
482         """Find an entry type in the section
483
484         Args:
485             etype: Entry type to find
486         Returns:
487             entry matching that type, or None if not found
488         """
489         for entry in self._entries.values():
490             if entry.etype == etype:
491                 return entry
492         return None
493
494     def GetEntryContents(self):
495         """Call ObtainContents() for each entry in the section
496         """
497         todo = self._entries.values()
498         for passnum in range(3):
499             next_todo = []
500             for entry in todo:
501                 if not entry.ObtainContents():
502                     next_todo.append(entry)
503             todo = next_todo
504             if not todo:
505                 break
506         if todo:
507             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
508                        todo)
509         return True
510
511     def _SetEntryOffsetSize(self, name, offset, size):
512         """Set the offset and size of an entry
513
514         Args:
515             name: Entry name to update
516             offset: New offset, or None to leave alone
517             size: New size, or None to leave alone
518         """
519         entry = self._entries.get(name)
520         if not entry:
521             self._Raise("Unable to set offset/size for unknown entry '%s'" %
522                         name)
523         entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
524                             else None, size)
525
526     def GetEntryOffsets(self):
527         """Handle entries that want to set the offset/size of other entries
528
529         This calls each entry's GetOffsets() method. If it returns a list
530         of entries to update, it updates them.
531         """
532         for entry in self._entries.values():
533             offset_dict = entry.GetOffsets()
534             for name, info in offset_dict.items():
535                 self._SetEntryOffsetSize(name, *info)
536
537
538     def CheckSize(self):
539         """Check that the section contents does not exceed its size, etc."""
540         contents_size = 0
541         for entry in self._entries.values():
542             contents_size = max(contents_size, entry.offset + entry.size)
543
544         contents_size -= self._skip_at_start
545
546         size = self.size
547         if not size:
548             size = self.pad_before + contents_size + self.pad_after
549             size = tools.Align(size, self.align_size)
550
551         if self.size and contents_size > self.size:
552             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
553                         (contents_size, contents_size, self.size, self.size))
554         if not self.size:
555             self.size = size
556         if self.size != tools.Align(self.size, self.align_size):
557             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
558                         (self.size, self.size, self.align_size,
559                          self.align_size))
560         return size
561
562     def ListEntries(self, entries, indent):
563         """List the files in the section"""
564         Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
565                            self.image_pos, None, self.offset, self)
566         for entry in self._entries.values():
567             entry.ListEntries(entries, indent + 1)
568
569     def LoadData(self, decomp=True):
570         for entry in self._entries.values():
571             entry.LoadData(decomp)
572         self.Detail('Loaded data')
573
574     def GetImage(self):
575         """Get the image containing this section
576
577         Note that a top-level section is actually an Image, so this function may
578         return self.
579
580         Returns:
581             Image object containing this section
582         """
583         if not self.section:
584             return self
585         return self.section.GetImage()
586
587     def GetSort(self):
588         """Check if the entries in this section will be sorted
589
590         Returns:
591             True if to be sorted, False if entries will be left in the order
592                 they appear in the device tree
593         """
594         return self._sort
595
596     def ReadData(self, decomp=True):
597         tout.Info("ReadData path='%s'" % self.GetPath())
598         parent_data = self.section.ReadData(True)
599         tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
600                   (self.GetPath(), self.offset, self.offset + self.size,
601                    self.size))
602         data = parent_data[self.offset:self.offset + self.size]
603         return data
604
605     def ReadChildData(self, child, decomp=True):
606         tout.Debug("ReadChildData for child '%s'" % child.GetPath())
607         parent_data = self.ReadData(True)
608         offset = child.offset - self._skip_at_start
609         tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
610                    (child.GetPath(), child.offset, self._skip_at_start, offset))
611         data = parent_data[offset:offset + child.size]
612         if decomp:
613             indata = data
614             data = tools.Decompress(indata, child.compress)
615             if child.uncomp_size:
616                 tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
617                             (child.GetPath(), len(indata), child.compress,
618                             len(data)))
619         return data
620
621     def WriteChildData(self, child):
622         return True
623
624     def SetAllowMissing(self, allow_missing):
625         """Set whether a section allows missing external blobs
626
627         Args:
628             allow_missing: True if allowed, False if not allowed
629         """
630         self.allow_missing = allow_missing
631         for entry in self._entries.values():
632             entry.SetAllowMissing(allow_missing)
633
634     def CheckMissing(self, missing_list):
635         """Check if any entries in this section have missing external blobs
636
637         If there are missing blobs, the entries are added to the list
638
639         Args:
640             missing_list: List of Entry objects to be added to
641         """
642         for entry in self._entries.values():
643             entry.CheckMissing(missing_list)