xref: /openbmc/u-boot/tools/binman/bsection.py (revision 3ebd892f)
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 CheckSize(self):
94        """Check that the section contents does not exceed its size, etc."""
95        contents_size = 0
96        for entry in self._entries.values():
97            contents_size = max(contents_size, entry.pos + entry.size)
98
99        contents_size -= self._skip_at_start
100
101        size = self._size
102        if not size:
103            size = self._pad_before + contents_size + self._pad_after
104            size = tools.Align(size, self._align_size)
105
106        if self._size and contents_size > self._size:
107            self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
108                       (contents_size, contents_size, self._size, self._size))
109        if not self._size:
110            self._size = size
111        if self._size != tools.Align(self._size, self._align_size):
112            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
113                  (self._size, self._size, self._align_size, self._align_size))
114        return size
115
116    def _Raise(self, msg):
117        """Raises an error for this section
118
119        Args:
120            msg: Error message to use in the raise string
121        Raises:
122            ValueError()
123        """
124        raise ValueError("Section '%s': %s" % (self._node.path, msg))
125
126    def GetPath(self):
127        """Get the path of an image (in the FDT)
128
129        Returns:
130            Full path of the node for this image
131        """
132        return self._node.path
133
134    def FindEntryType(self, etype):
135        """Find an entry type in the section
136
137        Args:
138            etype: Entry type to find
139        Returns:
140            entry matching that type, or None if not found
141        """
142        for entry in self._entries.values():
143            if entry.etype == etype:
144                return entry
145        return None
146
147    def GetEntryContents(self):
148        """Call ObtainContents() for each entry
149
150        This calls each entry's ObtainContents() a few times until they all
151        return True. We stop calling an entry's function once it returns
152        True. This allows the contents of one entry to depend on another.
153
154        After 3 rounds we give up since it's likely an error.
155        """
156        todo = self._entries.values()
157        for passnum in range(3):
158            next_todo = []
159            for entry in todo:
160                if not entry.ObtainContents():
161                    next_todo.append(entry)
162            todo = next_todo
163            if not todo:
164                break
165
166    def _SetEntryPosSize(self, name, pos, size):
167        """Set the position and size of an entry
168
169        Args:
170            name: Entry name to update
171            pos: New position
172            size: New size
173        """
174        entry = self._entries.get(name)
175        if not entry:
176            self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
177        entry.SetPositionSize(self._skip_at_start + pos, size)
178
179    def GetEntryPositions(self):
180        """Handle entries that want to set the position/size of other entries
181
182        This calls each entry's GetPositions() method. If it returns a list
183        of entries to update, it updates them.
184        """
185        for entry in self._entries.values():
186            pos_dict = entry.GetPositions()
187            for name, info in pos_dict.iteritems():
188                self._SetEntryPosSize(name, *info)
189
190    def PackEntries(self):
191        """Pack all entries into the section"""
192        pos = self._skip_at_start
193        for entry in self._entries.values():
194            pos = entry.Pack(pos)
195
196    def _SortEntries(self):
197        """Sort entries by position"""
198        entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
199        self._entries.clear()
200        for entry in entries:
201            self._entries[entry._node.name] = entry
202
203    def CheckEntries(self):
204        """Check that entries do not overlap or extend outside the section"""
205        if self._sort:
206            self._SortEntries()
207        pos = 0
208        prev_name = 'None'
209        for entry in self._entries.values():
210            entry.CheckPosition()
211            if (entry.pos < self._skip_at_start or
212                entry.pos >= self._skip_at_start + self._size):
213                entry.Raise("Position %#x (%d) is outside the section starting "
214                            "at %#x (%d)" %
215                            (entry.pos, entry.pos, self._skip_at_start,
216                             self._skip_at_start))
217            if entry.pos < pos:
218                entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
219                            "ending at %#x (%d)" %
220                            (entry.pos, entry.pos, prev_name, pos, pos))
221            pos = entry.pos + entry.size
222            prev_name = entry.GetPath()
223
224    def ProcessEntryContents(self):
225        """Call the ProcessContents() method for each entry
226
227        This is intended to adjust the contents as needed by the entry type.
228        """
229        for entry in self._entries.values():
230            entry.ProcessContents()
231
232    def WriteSymbols(self):
233        """Write symbol values into binary files for access at run time"""
234        for entry in self._entries.values():
235            entry.WriteSymbols(self)
236
237    def BuildSection(self, fd, base_pos):
238        """Write the section to a file"""
239        fd.seek(base_pos)
240        fd.write(self.GetData())
241
242    def GetData(self):
243        """Write the section to a file"""
244        section_data = chr(self._pad_byte) * self._size
245
246        for entry in self._entries.values():
247            data = entry.GetData()
248            base = self._pad_before + entry.pos - self._skip_at_start
249            section_data = (section_data[:base] + data +
250                            section_data[base + len(data):])
251        return section_data
252
253    def LookupSymbol(self, sym_name, optional, msg):
254        """Look up a symbol in an ELF file
255
256        Looks up a symbol in an ELF file. Only entry types which come from an
257        ELF image can be used by this function.
258
259        At present the only entry property supported is pos.
260
261        Args:
262            sym_name: Symbol name in the ELF file to look up in the format
263                _binman_<entry>_prop_<property> where <entry> is the name of
264                the entry and <property> is the property to find (e.g.
265                _binman_u_boot_prop_pos). As a special case, you can append
266                _any to <entry> to have it search for any matching entry. E.g.
267                _binman_u_boot_any_prop_pos will match entries called u-boot,
268                u-boot-img and u-boot-nodtb)
269            optional: True if the symbol is optional. If False this function
270                will raise if the symbol is not found
271            msg: Message to display if an error occurs
272
273        Returns:
274            Value that should be assigned to that symbol, or None if it was
275                optional and not found
276
277        Raises:
278            ValueError if the symbol is invalid or not found, or references a
279                property which is not supported
280        """
281        m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
282        if not m:
283            raise ValueError("%s: Symbol '%s' has invalid format" %
284                             (msg, sym_name))
285        entry_name, prop_name = m.groups()
286        entry_name = entry_name.replace('_', '-')
287        entry = self._entries.get(entry_name)
288        if not entry:
289            if entry_name.endswith('-any'):
290                root = entry_name[:-4]
291                for name in self._entries:
292                    if name.startswith(root):
293                        rest = name[len(root):]
294                        if rest in ['', '-img', '-nodtb']:
295                            entry = self._entries[name]
296        if not entry:
297            err = ("%s: Entry '%s' not found in list (%s)" %
298                   (msg, entry_name, ','.join(self._entries.keys())))
299            if optional:
300                print('Warning: %s' % err, file=sys.stderr)
301                return None
302            raise ValueError(err)
303        if prop_name == 'pos':
304            return entry.pos
305        else:
306            raise ValueError("%s: No such property '%s'" % (msg, prop_name))
307
308    def GetEntries(self):
309        return self._entries
310
311    def WriteMap(self, fd, indent):
312        """Write a map of the section to a .map file
313
314        Args:
315            fd: File to write the map to
316        """
317        for entry in self._entries.values():
318            entry.WriteMap(fd, indent)
319