xref: /openbmc/u-boot/tools/binman/bsection.py (revision 78a88f79)
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 position 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 position, 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 position of entries. For example,
36            if _pad_before is 16, then the first entry would start at 16.
37            An entry with pos = 20 would in fact be written at position 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 positions such that a
41             memory address (like 0xff800000) is the first entry position.
42             This causes _skip_at_start to be set to the starting memory
43             address.
44        _name_prefix: Prefix to add to the name of all entries within this
45            section
46        _entries: OrderedDict() of entries
47    """
48    def __init__(self, name, node, test=False):
49        global entry
50        global Entry
51        import entry
52        from entry import Entry
53
54        self._node = node
55        self._size = None
56        self._align_size = None
57        self._pad_before = 0
58        self._pad_after = 0
59        self._pad_byte = 0
60        self._sort = False
61        self._skip_at_start = 0
62        self._end_4gb = False
63        self._name_prefix = ''
64        self._entries = OrderedDict()
65        if not test:
66            self._ReadNode()
67            self._ReadEntries()
68
69    def _ReadNode(self):
70        """Read properties from the section node"""
71        self._size = fdt_util.GetInt(self._node, 'size')
72        self._align_size = fdt_util.GetInt(self._node, 'align-size')
73        if tools.NotPowerOfTwo(self._align_size):
74            self._Raise("Alignment size %s must be a power of two" %
75                        self._align_size)
76        self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
77        self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
78        self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
79        self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
80        self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
81        if self._end_4gb and not self._size:
82            self._Raise("Section size must be provided when using end-at-4gb")
83        if self._end_4gb:
84            self._skip_at_start = 0x100000000 - self._size
85        self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
86
87    def _ReadEntries(self):
88        for node in self._node.subnodes:
89            entry = Entry.Create(self, node)
90            entry.SetPrefix(self._name_prefix)
91            self._entries[node.name] = entry
92
93    def AddMissingProperties(self):
94        for entry in self._entries.values():
95            entry.AddMissingProperties()
96
97    def SetCalculatedProperties(self):
98        for entry in self._entries.values():
99            entry.SetCalculatedProperties()
100
101    def ProcessFdt(self, fdt):
102        todo = self._entries.values()
103        for passnum in range(3):
104            next_todo = []
105            for entry in todo:
106                if not entry.ProcessFdt(fdt):
107                    next_todo.append(entry)
108            todo = next_todo
109            if not todo:
110                break
111        if todo:
112            self._Raise('Internal error: Could not complete processing of Fdt: '
113                        'remaining %s' % todo)
114        return True
115
116    def CheckSize(self):
117        """Check that the section contents does not exceed its size, etc."""
118        contents_size = 0
119        for entry in self._entries.values():
120            contents_size = max(contents_size, entry.pos + entry.size)
121
122        contents_size -= self._skip_at_start
123
124        size = self._size
125        if not size:
126            size = self._pad_before + contents_size + self._pad_after
127            size = tools.Align(size, self._align_size)
128
129        if self._size and contents_size > self._size:
130            self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
131                       (contents_size, contents_size, self._size, self._size))
132        if not self._size:
133            self._size = size
134        if self._size != tools.Align(self._size, self._align_size):
135            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
136                  (self._size, self._size, self._align_size, self._align_size))
137        return size
138
139    def _Raise(self, msg):
140        """Raises an error for this section
141
142        Args:
143            msg: Error message to use in the raise string
144        Raises:
145            ValueError()
146        """
147        raise ValueError("Section '%s': %s" % (self._node.path, msg))
148
149    def GetPath(self):
150        """Get the path of an image (in the FDT)
151
152        Returns:
153            Full path of the node for this image
154        """
155        return self._node.path
156
157    def FindEntryType(self, etype):
158        """Find an entry type in the section
159
160        Args:
161            etype: Entry type to find
162        Returns:
163            entry matching that type, or None if not found
164        """
165        for entry in self._entries.values():
166            if entry.etype == etype:
167                return entry
168        return None
169
170    def GetEntryContents(self):
171        """Call ObtainContents() for each entry
172
173        This calls each entry's ObtainContents() a few times until they all
174        return True. We stop calling an entry's function once it returns
175        True. This allows the contents of one entry to depend on another.
176
177        After 3 rounds we give up since it's likely an error.
178        """
179        todo = self._entries.values()
180        for passnum in range(3):
181            next_todo = []
182            for entry in todo:
183                if not entry.ObtainContents():
184                    next_todo.append(entry)
185            todo = next_todo
186            if not todo:
187                break
188        if todo:
189            self._Raise('Internal error: Could not complete processing of '
190                        'contents: remaining %s' % todo)
191        return True
192
193    def _SetEntryPosSize(self, name, pos, size):
194        """Set the position and size of an entry
195
196        Args:
197            name: Entry name to update
198            pos: New position
199            size: New size
200        """
201        entry = self._entries.get(name)
202        if not entry:
203            self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
204        entry.SetPositionSize(self._skip_at_start + pos, size)
205
206    def GetEntryPositions(self):
207        """Handle entries that want to set the position/size of other entries
208
209        This calls each entry's GetPositions() method. If it returns a list
210        of entries to update, it updates them.
211        """
212        for entry in self._entries.values():
213            pos_dict = entry.GetPositions()
214            for name, info in pos_dict.iteritems():
215                self._SetEntryPosSize(name, *info)
216
217    def PackEntries(self):
218        """Pack all entries into the section"""
219        pos = self._skip_at_start
220        for entry in self._entries.values():
221            pos = entry.Pack(pos)
222
223    def _SortEntries(self):
224        """Sort entries by position"""
225        entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
226        self._entries.clear()
227        for entry in entries:
228            self._entries[entry._node.name] = entry
229
230    def CheckEntries(self):
231        """Check that entries do not overlap or extend outside the section"""
232        if self._sort:
233            self._SortEntries()
234        pos = 0
235        prev_name = 'None'
236        for entry in self._entries.values():
237            entry.CheckPosition()
238            if (entry.pos < self._skip_at_start or
239                entry.pos >= self._skip_at_start + self._size):
240                entry.Raise("Position %#x (%d) is outside the section starting "
241                            "at %#x (%d)" %
242                            (entry.pos, entry.pos, self._skip_at_start,
243                             self._skip_at_start))
244            if entry.pos < pos:
245                entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
246                            "ending at %#x (%d)" %
247                            (entry.pos, entry.pos, prev_name, pos, pos))
248            pos = entry.pos + entry.size
249            prev_name = entry.GetPath()
250
251    def ProcessEntryContents(self):
252        """Call the ProcessContents() method for each entry
253
254        This is intended to adjust the contents as needed by the entry type.
255        """
256        for entry in self._entries.values():
257            entry.ProcessContents()
258
259    def WriteSymbols(self):
260        """Write symbol values into binary files for access at run time"""
261        for entry in self._entries.values():
262            entry.WriteSymbols(self)
263
264    def BuildSection(self, fd, base_pos):
265        """Write the section to a file"""
266        fd.seek(base_pos)
267        fd.write(self.GetData())
268
269    def GetData(self):
270        """Write the section to a file"""
271        section_data = chr(self._pad_byte) * self._size
272
273        for entry in self._entries.values():
274            data = entry.GetData()
275            base = self._pad_before + entry.pos - self._skip_at_start
276            section_data = (section_data[:base] + data +
277                            section_data[base + len(data):])
278        return section_data
279
280    def LookupSymbol(self, sym_name, optional, msg):
281        """Look up a symbol in an ELF file
282
283        Looks up a symbol in an ELF file. Only entry types which come from an
284        ELF image can be used by this function.
285
286        At present the only entry property supported is pos.
287
288        Args:
289            sym_name: Symbol name in the ELF file to look up in the format
290                _binman_<entry>_prop_<property> where <entry> is the name of
291                the entry and <property> is the property to find (e.g.
292                _binman_u_boot_prop_pos). As a special case, you can append
293                _any to <entry> to have it search for any matching entry. E.g.
294                _binman_u_boot_any_prop_pos will match entries called u-boot,
295                u-boot-img and u-boot-nodtb)
296            optional: True if the symbol is optional. If False this function
297                will raise if the symbol is not found
298            msg: Message to display if an error occurs
299
300        Returns:
301            Value that should be assigned to that symbol, or None if it was
302                optional and not found
303
304        Raises:
305            ValueError if the symbol is invalid or not found, or references a
306                property which is not supported
307        """
308        m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
309        if not m:
310            raise ValueError("%s: Symbol '%s' has invalid format" %
311                             (msg, sym_name))
312        entry_name, prop_name = m.groups()
313        entry_name = entry_name.replace('_', '-')
314        entry = self._entries.get(entry_name)
315        if not entry:
316            if entry_name.endswith('-any'):
317                root = entry_name[:-4]
318                for name in self._entries:
319                    if name.startswith(root):
320                        rest = name[len(root):]
321                        if rest in ['', '-img', '-nodtb']:
322                            entry = self._entries[name]
323        if not entry:
324            err = ("%s: Entry '%s' not found in list (%s)" %
325                   (msg, entry_name, ','.join(self._entries.keys())))
326            if optional:
327                print('Warning: %s' % err, file=sys.stderr)
328                return None
329            raise ValueError(err)
330        if prop_name == 'pos':
331            return entry.pos
332        else:
333            raise ValueError("%s: No such property '%s'" % (msg, prop_name))
334
335    def GetEntries(self):
336        return self._entries
337
338    def WriteMap(self, fd, indent):
339        """Write a map of the section to a .map file
340
341        Args:
342            fd: File to write the map to
343        """
344        for entry in self._entries.values():
345            entry.WriteMap(fd, indent)
346