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