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