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