xref: /openbmc/u-boot/tools/binman/bsection.py (revision 5c8fd32b)
1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2018 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Base class for sections (collections of entries)
6#
7
8from __future__ import print_function
9
10from collections import OrderedDict
11from sets import Set
12import sys
13
14import fdt_util
15import re
16import state
17import tools
18
19class Section(object):
20    """A section which contains multiple entries
21
22    A section represents a collection of entries. There must be one or more
23    sections in an image. Sections are used to group entries together.
24
25    Attributes:
26        _node: Node object that contains the section definition in device tree
27        _parent_section: Parent Section object which created this Section
28        _size: Section size in bytes, or None if not known yet
29        _align_size: Section size alignment, or None
30        _pad_before: Number of bytes before the first entry starts. This
31            effectively changes the place where entry offset 0 starts
32        _pad_after: Number of bytes after the last entry ends. The last
33            entry will finish on or before this boundary
34        _pad_byte: Byte to use to pad the section where there is no entry
35        _sort: True if entries should be sorted by offset, False if they
36            must be in-order in the device tree description
37        _skip_at_start: Number of bytes before the first entry starts. These
38            effectively adjust the starting offset of entries. For example,
39            if _pad_before is 16, then the first entry would start at 16.
40            An entry with offset = 20 would in fact be written at offset 4
41            in the image file.
42        _end_4gb: Indicates that the section ends at the 4GB boundary. This is
43            used for x86 images, which want to use offsets such that a memory
44            address (like 0xff800000) is the first entry offset. This causes
45            _skip_at_start to be set to the starting memory address.
46        _name_prefix: Prefix to add to the name of all entries within this
47            section
48        _entries: OrderedDict() of entries
49    """
50    def __init__(self, name, parent_section, node, image, test=False):
51        global entry
52        global Entry
53        import entry
54        from entry import Entry
55
56        self._parent_section = parent_section
57        self._name = name
58        self._node = node
59        self._image = image
60        self._offset = 0
61        self._size = None
62        self._align_size = None
63        self._pad_before = 0
64        self._pad_after = 0
65        self._pad_byte = 0
66        self._sort = False
67        self._skip_at_start = None
68        self._end_4gb = False
69        self._name_prefix = ''
70        self._entries = OrderedDict()
71        self._image_pos = None
72        if not test:
73            self._ReadNode()
74            self._ReadEntries()
75
76    def _ReadNode(self):
77        """Read properties from the section node"""
78        self._size = fdt_util.GetInt(self._node, 'size')
79        self._align_size = fdt_util.GetInt(self._node, 'align-size')
80        if tools.NotPowerOfTwo(self._align_size):
81            self._Raise("Alignment size %s must be a power of two" %
82                        self._align_size)
83        self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
84        self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
85        self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
86        self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
87        self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
88        self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
89        if self._end_4gb:
90            if not self._size:
91                self._Raise("Section size must be provided when using end-at-4gb")
92            if self._skip_at_start is not None:
93                self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
94            else:
95                self._skip_at_start = 0x100000000 - self._size
96        else:
97            if self._skip_at_start is None:
98                self._skip_at_start = 0
99        self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
100
101    def _ReadEntries(self):
102        for node in self._node.subnodes:
103            if node.name == 'hash':
104                continue
105            entry = Entry.Create(self, node)
106            entry.SetPrefix(self._name_prefix)
107            self._entries[node.name] = entry
108
109    def GetFdtSet(self):
110        """Get the set of device tree files used by this image"""
111        fdt_set = Set()
112        for entry in self._entries.values():
113            fdt_set.update(entry.GetFdtSet())
114        return fdt_set
115
116    def SetOffset(self, offset):
117        self._offset = offset
118
119    def ExpandEntries(self):
120        for entry in self._entries.values():
121            entry.ExpandEntries()
122
123    def AddMissingProperties(self):
124        """Add new properties to the device tree as needed for this entry"""
125        for prop in ['offset', 'size', 'image-pos']:
126            if not prop in self._node.props:
127                state.AddZeroProp(self._node, prop)
128        state.CheckAddHashProp(self._node)
129        for entry in self._entries.values():
130            entry.AddMissingProperties()
131
132    def SetCalculatedProperties(self):
133        state.SetInt(self._node, 'offset', self._offset)
134        state.SetInt(self._node, 'size', self._size)
135        image_pos = self._image_pos
136        if self._parent_section:
137            image_pos -= self._parent_section.GetRootSkipAtStart()
138        state.SetInt(self._node, 'image-pos', image_pos)
139        for entry in self._entries.values():
140            entry.SetCalculatedProperties()
141
142    def ProcessFdt(self, fdt):
143        todo = self._entries.values()
144        for passnum in range(3):
145            next_todo = []
146            for entry in todo:
147                if not entry.ProcessFdt(fdt):
148                    next_todo.append(entry)
149            todo = next_todo
150            if not todo:
151                break
152        if todo:
153            self._Raise('Internal error: Could not complete processing of Fdt: '
154                        'remaining %s' % todo)
155        return True
156
157    def CheckSize(self):
158        """Check that the section contents does not exceed its size, etc."""
159        contents_size = 0
160        for entry in self._entries.values():
161            contents_size = max(contents_size, entry.offset + entry.size)
162
163        contents_size -= self._skip_at_start
164
165        size = self._size
166        if not size:
167            size = self._pad_before + contents_size + self._pad_after
168            size = tools.Align(size, self._align_size)
169
170        if self._size and contents_size > self._size:
171            self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
172                       (contents_size, contents_size, self._size, self._size))
173        if not self._size:
174            self._size = size
175        if self._size != tools.Align(self._size, self._align_size):
176            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
177                  (self._size, self._size, self._align_size, self._align_size))
178        return size
179
180    def _Raise(self, msg):
181        """Raises an error for this section
182
183        Args:
184            msg: Error message to use in the raise string
185        Raises:
186            ValueError()
187        """
188        raise ValueError("Section '%s': %s" % (self._node.path, msg))
189
190    def GetPath(self):
191        """Get the path of an image (in the FDT)
192
193        Returns:
194            Full path of the node for this image
195        """
196        return self._node.path
197
198    def FindEntryType(self, etype):
199        """Find an entry type in the section
200
201        Args:
202            etype: Entry type to find
203        Returns:
204            entry matching that type, or None if not found
205        """
206        for entry in self._entries.values():
207            if entry.etype == etype:
208                return entry
209        return None
210
211    def GetEntryContents(self):
212        """Call ObtainContents() for each entry
213
214        This calls each entry's ObtainContents() a few times until they all
215        return True. We stop calling an entry's function once it returns
216        True. This allows the contents of one entry to depend on another.
217
218        After 3 rounds we give up since it's likely an error.
219        """
220        todo = self._entries.values()
221        for passnum in range(3):
222            next_todo = []
223            for entry in todo:
224                if not entry.ObtainContents():
225                    next_todo.append(entry)
226            todo = next_todo
227            if not todo:
228                break
229        if todo:
230            self._Raise('Internal error: Could not complete processing of '
231                        'contents: remaining %s' % todo)
232        return True
233
234    def _SetEntryOffsetSize(self, name, offset, size):
235        """Set the offset and size of an entry
236
237        Args:
238            name: Entry name to update
239            offset: New offset
240            size: New size
241        """
242        entry = self._entries.get(name)
243        if not entry:
244            self._Raise("Unable to set offset/size for unknown entry '%s'" %
245                        name)
246        entry.SetOffsetSize(self._skip_at_start + offset, size)
247
248    def GetEntryOffsets(self):
249        """Handle entries that want to set the offset/size of other entries
250
251        This calls each entry's GetOffsets() method. If it returns a list
252        of entries to update, it updates them.
253        """
254        for entry in self._entries.values():
255            offset_dict = entry.GetOffsets()
256            for name, info in offset_dict.iteritems():
257                self._SetEntryOffsetSize(name, *info)
258
259    def PackEntries(self):
260        """Pack all entries into the section"""
261        offset = self._skip_at_start
262        for entry in self._entries.values():
263            offset = entry.Pack(offset)
264        self._size = self.CheckSize()
265
266    def _SortEntries(self):
267        """Sort entries by offset"""
268        entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
269        self._entries.clear()
270        for entry in entries:
271            self._entries[entry._node.name] = entry
272
273    def _ExpandEntries(self):
274        """Expand any entries that are permitted to"""
275        exp_entry = None
276        for entry in self._entries.values():
277            if exp_entry:
278                exp_entry.ExpandToLimit(entry.offset)
279                exp_entry = None
280            if entry.expand_size:
281                exp_entry = entry
282        if exp_entry:
283            exp_entry.ExpandToLimit(self._size)
284
285    def CheckEntries(self):
286        """Check that entries do not overlap or extend outside the section
287
288        This also sorts entries, if needed and expands
289        """
290        if self._sort:
291            self._SortEntries()
292        self._ExpandEntries()
293        offset = 0
294        prev_name = 'None'
295        for entry in self._entries.values():
296            entry.CheckOffset()
297            if (entry.offset < self._skip_at_start or
298                entry.offset + entry.size > self._skip_at_start + self._size):
299                entry.Raise("Offset %#x (%d) is outside the section starting "
300                            "at %#x (%d)" %
301                            (entry.offset, entry.offset, self._skip_at_start,
302                             self._skip_at_start))
303            if entry.offset < offset:
304                entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
305                            "ending at %#x (%d)" %
306                            (entry.offset, entry.offset, prev_name, offset, offset))
307            offset = entry.offset + entry.size
308            prev_name = entry.GetPath()
309
310    def SetImagePos(self, image_pos):
311        self._image_pos = image_pos
312        for entry in self._entries.values():
313            entry.SetImagePos(image_pos)
314
315    def ProcessEntryContents(self):
316        """Call the ProcessContents() method for each entry
317
318        This is intended to adjust the contents as needed by the entry type.
319        """
320        for entry in self._entries.values():
321            entry.ProcessContents()
322
323    def WriteSymbols(self):
324        """Write symbol values into binary files for access at run time"""
325        for entry in self._entries.values():
326            entry.WriteSymbols(self)
327
328    def BuildSection(self, fd, base_offset):
329        """Write the section to a file"""
330        fd.seek(base_offset)
331        fd.write(self.GetData())
332
333    def GetData(self):
334        """Get the contents of the section"""
335        section_data = chr(self._pad_byte) * self._size
336
337        for entry in self._entries.values():
338            data = entry.GetData()
339            base = self._pad_before + entry.offset - self._skip_at_start
340            section_data = (section_data[:base] + data +
341                            section_data[base + len(data):])
342        return section_data
343
344    def LookupSymbol(self, sym_name, optional, msg):
345        """Look up a symbol in an ELF file
346
347        Looks up a symbol in an ELF file. Only entry types which come from an
348        ELF image can be used by this function.
349
350        At present the only entry property supported is offset.
351
352        Args:
353            sym_name: Symbol name in the ELF file to look up in the format
354                _binman_<entry>_prop_<property> where <entry> is the name of
355                the entry and <property> is the property to find (e.g.
356                _binman_u_boot_prop_offset). As a special case, you can append
357                _any to <entry> to have it search for any matching entry. E.g.
358                _binman_u_boot_any_prop_offset will match entries called u-boot,
359                u-boot-img and u-boot-nodtb)
360            optional: True if the symbol is optional. If False this function
361                will raise if the symbol is not found
362            msg: Message to display if an error occurs
363
364        Returns:
365            Value that should be assigned to that symbol, or None if it was
366                optional and not found
367
368        Raises:
369            ValueError if the symbol is invalid or not found, or references a
370                property which is not supported
371        """
372        m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
373        if not m:
374            raise ValueError("%s: Symbol '%s' has invalid format" %
375                             (msg, sym_name))
376        entry_name, prop_name = m.groups()
377        entry_name = entry_name.replace('_', '-')
378        entry = self._entries.get(entry_name)
379        if not entry:
380            if entry_name.endswith('-any'):
381                root = entry_name[:-4]
382                for name in self._entries:
383                    if name.startswith(root):
384                        rest = name[len(root):]
385                        if rest in ['', '-img', '-nodtb']:
386                            entry = self._entries[name]
387        if not entry:
388            err = ("%s: Entry '%s' not found in list (%s)" %
389                   (msg, entry_name, ','.join(self._entries.keys())))
390            if optional:
391                print('Warning: %s' % err, file=sys.stderr)
392                return None
393            raise ValueError(err)
394        if prop_name == 'offset':
395            return entry.offset
396        elif prop_name == 'image_pos':
397            return entry.image_pos
398        else:
399            raise ValueError("%s: No such property '%s'" % (msg, prop_name))
400
401    def GetEntries(self):
402        """Get the number of entries in a section
403
404        Returns:
405            Number of entries in a section
406        """
407        return self._entries
408
409    def GetSize(self):
410        """Get the size of a section in bytes
411
412        This is only meaningful if the section has a pre-defined size, or the
413        entries within it have been packed, so that the size has been
414        calculated.
415
416        Returns:
417            Entry size in bytes
418        """
419        return self._size
420
421    def WriteMap(self, fd, indent):
422        """Write a map of the section to a .map file
423
424        Args:
425            fd: File to write the map to
426        """
427        Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
428                           self._image_pos)
429        for entry in self._entries.values():
430            entry.WriteMap(fd, indent + 1)
431
432    def GetContentsByPhandle(self, phandle, source_entry):
433        """Get the data contents of an entry specified by a phandle
434
435        This uses a phandle to look up a node and and find the entry
436        associated with it. Then it returnst he contents of that entry.
437
438        Args:
439            phandle: Phandle to look up (integer)
440            source_entry: Entry containing that phandle (used for error
441                reporting)
442
443        Returns:
444            data from associated entry (as a string), or None if not found
445        """
446        node = self._node.GetFdt().LookupPhandle(phandle)
447        if not node:
448            source_entry.Raise("Cannot find node for phandle %d" % phandle)
449        for entry in self._entries.values():
450            if entry._node == node:
451                return entry.GetData()
452        source_entry.Raise("Cannot find entry for node '%s'" % node.name)
453
454    def ExpandSize(self, size):
455        if size != self._size:
456            self._size = size
457
458    def GetRootSkipAtStart(self):
459        if self._parent_section:
460            return self._parent_section.GetRootSkipAtStart()
461        return self._skip_at_start
462
463    def GetImageSize(self):
464        return self._image._size
465