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