bsection.py 19.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2018 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Base class for sections (collections of entries)
#

from __future__ import print_function

from collections import OrderedDict
import sys

13
from entry import Entry
14 15
import fdt_util
import re
16
import state
17 18 19 20 21 22 23 24 25 26
import tools

class Section(object):
    """A section which contains multiple entries

    A section represents a collection of entries. There must be one or more
    sections in an image. Sections are used to group entries together.

    Attributes:
        _node: Node object that contains the section definition in device tree
27
        _parent_section: Parent Section object which created this Section
28 29 30
        _size: Section size in bytes, or None if not known yet
        _align_size: Section size alignment, or None
        _pad_before: Number of bytes before the first entry starts. This
31
            effectively changes the place where entry offset 0 starts
32 33 34
        _pad_after: Number of bytes after the last entry ends. The last
            entry will finish on or before this boundary
        _pad_byte: Byte to use to pad the section where there is no entry
35
        _sort: True if entries should be sorted by offset, False if they
36 37
            must be in-order in the device tree description
        _skip_at_start: Number of bytes before the first entry starts. These
38
            effectively adjust the starting offset of entries. For example,
39
            if _pad_before is 16, then the first entry would start at 16.
40
            An entry with offset = 20 would in fact be written at offset 4
41 42
            in the image file.
        _end_4gb: Indicates that the section ends at the 4GB boundary. This is
43 44 45
            used for x86 images, which want to use offsets such that a memory
            address (like 0xff800000) is the first entry offset. This causes
            _skip_at_start to be set to the starting memory address.
46 47
        _name_prefix: Prefix to add to the name of all entries within this
            section
48
        _entries: OrderedDict() of entries
49 50
        _orig_offset: Original offset value read from node
        _orig_size: Original size value read from node
51
    """
52
    def __init__(self, name, parent_section, node, image, test=False):
53 54 55 56 57
        global entry
        global Entry
        import entry
        from entry import Entry

58
        self._parent_section = parent_section
59
        self._name = name
60
        self._node = node
61
        self._image = image
62
        self._offset = None
63 64 65 66 67 68
        self._size = None
        self._align_size = None
        self._pad_before = 0
        self._pad_after = 0
        self._pad_byte = 0
        self._sort = False
69
        self._skip_at_start = None
70
        self._end_4gb = False
71
        self._name_prefix = ''
72
        self._entries = OrderedDict()
S
Simon Glass 已提交
73
        self._image_pos = None
74 75 76 77 78 79
        if not test:
            self._ReadNode()
            self._ReadEntries()

    def _ReadNode(self):
        """Read properties from the section node"""
80
        self._offset = fdt_util.GetInt(self._node, 'offset')
81
        self._size = fdt_util.GetInt(self._node, 'size')
82 83
        self._orig_offset = self._offset
        self._orig_size = self._size
84 85 86 87 88 89 90
        self._align_size = fdt_util.GetInt(self._node, 'align-size')
        if tools.NotPowerOfTwo(self._align_size):
            self._Raise("Alignment size %s must be a power of two" %
                        self._align_size)
        self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
        self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
        self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
91
        self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
92
        self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
93
        self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
94
        if self._end_4gb:
95 96 97 98 99 100 101 102 103
            if not self._size:
                self._Raise("Section size must be provided when using end-at-4gb")
            if self._skip_at_start is not None:
                self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
            else:
                self._skip_at_start = 0x100000000 - self._size
        else:
            if self._skip_at_start is None:
                self._skip_at_start = 0
104
        self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
105 106 107

    def _ReadEntries(self):
        for node in self._node.subnodes:
S
Simon Glass 已提交
108 109
            if node.name == 'hash':
                continue
110 111 112
            entry = Entry.Create(self, node)
            entry.SetPrefix(self._name_prefix)
            self._entries[node.name] = entry
113

114 115
    def GetFdtSet(self):
        """Get the set of device tree files used by this image"""
S
Simon Glass 已提交
116
        fdt_set = set()
117 118 119 120
        for entry in self._entries.values():
            fdt_set.update(entry.GetFdtSet())
        return fdt_set

121 122 123
    def SetOffset(self, offset):
        self._offset = offset

S
Simon Glass 已提交
124 125 126 127
    def ExpandEntries(self):
        for entry in self._entries.values():
            entry.ExpandEntries()

128
    def AddMissingProperties(self):
129
        """Add new properties to the device tree as needed for this entry"""
130
        for prop in ['offset', 'size', 'image-pos']:
131
            if not prop in self._node.props:
132
                state.AddZeroProp(self._node, prop)
S
Simon Glass 已提交
133
        state.CheckAddHashProp(self._node)
134 135 136 137
        for entry in self._entries.values():
            entry.AddMissingProperties()

    def SetCalculatedProperties(self):
138
        state.SetInt(self._node, 'offset', self._offset or 0)
139
        state.SetInt(self._node, 'size', self._size)
S
Simon Glass 已提交
140 141 142 143
        image_pos = self._image_pos
        if self._parent_section:
            image_pos -= self._parent_section.GetRootSkipAtStart()
        state.SetInt(self._node, 'image-pos', image_pos)
144 145 146
        for entry in self._entries.values():
            entry.SetCalculatedProperties()

S
Simon Glass 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    def ProcessFdt(self, fdt):
        todo = self._entries.values()
        for passnum in range(3):
            next_todo = []
            for entry in todo:
                if not entry.ProcessFdt(fdt):
                    next_todo.append(entry)
            todo = next_todo
            if not todo:
                break
        if todo:
            self._Raise('Internal error: Could not complete processing of Fdt: '
                        'remaining %s' % todo)
        return True

162 163 164 165
    def CheckSize(self):
        """Check that the section contents does not exceed its size, etc."""
        contents_size = 0
        for entry in self._entries.values():
166
            contents_size = max(contents_size, entry.offset + entry.size)
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

        contents_size -= self._skip_at_start

        size = self._size
        if not size:
            size = self._pad_before + contents_size + self._pad_after
            size = tools.Align(size, self._align_size)

        if self._size and contents_size > self._size:
            self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
                       (contents_size, contents_size, self._size, self._size))
        if not self._size:
            self._size = size
        if self._size != tools.Align(self._size, self._align_size):
            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
                  (self._size, self._size, self._align_size, self._align_size))
        return size

    def _Raise(self, msg):
        """Raises an error for this section

        Args:
            msg: Error message to use in the raise string
        Raises:
            ValueError()
        """
        raise ValueError("Section '%s': %s" % (self._node.path, msg))

    def GetPath(self):
        """Get the path of an image (in the FDT)

        Returns:
            Full path of the node for this image
        """
        return self._node.path

    def FindEntryType(self, etype):
        """Find an entry type in the section

        Args:
            etype: Entry type to find
        Returns:
            entry matching that type, or None if not found
        """
        for entry in self._entries.values():
            if entry.etype == etype:
                return entry
        return None

    def GetEntryContents(self):
        """Call ObtainContents() for each entry

        This calls each entry's ObtainContents() a few times until they all
        return True. We stop calling an entry's function once it returns
        True. This allows the contents of one entry to depend on another.

        After 3 rounds we give up since it's likely an error.
        """
        todo = self._entries.values()
        for passnum in range(3):
            next_todo = []
            for entry in todo:
                if not entry.ObtainContents():
                    next_todo.append(entry)
            todo = next_todo
            if not todo:
                break
234 235 236 237
        if todo:
            self._Raise('Internal error: Could not complete processing of '
                        'contents: remaining %s' % todo)
        return True
238

239 240
    def _SetEntryOffsetSize(self, name, offset, size):
        """Set the offset and size of an entry
241 242 243

        Args:
            name: Entry name to update
244 245
            offset: New offset, or None to leave alone
            size: New size, or None to leave alone
246 247 248
        """
        entry = self._entries.get(name)
        if not entry:
249 250
            self._Raise("Unable to set offset/size for unknown entry '%s'" %
                        name)
251 252
        entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
                            size)
253

254 255
    def GetEntryOffsets(self):
        """Handle entries that want to set the offset/size of other entries
256

257
        This calls each entry's GetOffsets() method. If it returns a list
258 259 260
        of entries to update, it updates them.
        """
        for entry in self._entries.values():
261
            offset_dict = entry.GetOffsets()
262
            for name, info in offset_dict.items():
263
                self._SetEntryOffsetSize(name, *info)
264

265 266 267 268 269 270 271
    def ResetForPack(self):
        """Reset offset/size fields so that packing can be done again"""
        self._offset = self._orig_offset
        self._size = self._orig_size
        for entry in self._entries.values():
            entry.ResetForPack()

272 273
    def PackEntries(self):
        """Pack all entries into the section"""
274
        offset = self._skip_at_start
275
        for entry in self._entries.values():
276 277
            offset = entry.Pack(offset)
        self._size = self.CheckSize()
278 279

    def _SortEntries(self):
280 281
        """Sort entries by offset"""
        entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
282 283 284 285
        self._entries.clear()
        for entry in entries:
            self._entries[entry._node.name] = entry

S
Simon Glass 已提交
286 287 288 289 290 291 292 293 294 295 296 297
    def _ExpandEntries(self):
        """Expand any entries that are permitted to"""
        exp_entry = None
        for entry in self._entries.values():
            if exp_entry:
                exp_entry.ExpandToLimit(entry.offset)
                exp_entry = None
            if entry.expand_size:
                exp_entry = entry
        if exp_entry:
            exp_entry.ExpandToLimit(self._size)

298
    def CheckEntries(self):
S
Simon Glass 已提交
299 300 301 302
        """Check that entries do not overlap or extend outside the section

        This also sorts entries, if needed and expands
        """
303 304
        if self._sort:
            self._SortEntries()
S
Simon Glass 已提交
305
        self._ExpandEntries()
306
        offset = 0
307 308
        prev_name = 'None'
        for entry in self._entries.values():
309 310
            entry.CheckOffset()
            if (entry.offset < self._skip_at_start or
S
Simon Glass 已提交
311
                entry.offset + entry.size > self._skip_at_start + self._size):
312
                entry.Raise("Offset %#x (%d) is outside the section starting "
313
                            "at %#x (%d)" %
314
                            (entry.offset, entry.offset, self._skip_at_start,
315
                             self._skip_at_start))
316 317
            if entry.offset < offset:
                entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
318
                            "ending at %#x (%d)" %
319 320
                            (entry.offset, entry.offset, prev_name, offset, offset))
            offset = entry.offset + entry.size
321 322
            prev_name = entry.GetPath()

323 324 325 326 327
    def SetImagePos(self, image_pos):
        self._image_pos = image_pos
        for entry in self._entries.values():
            entry.SetImagePos(image_pos)

328 329 330 331
    def ProcessEntryContents(self):
        """Call the ProcessContents() method for each entry

        This is intended to adjust the contents as needed by the entry type.
332 333 334

        Returns:
            True if no entries needed to change their size
335
        """
336
        sizes_ok = True
337
        for entry in self._entries.values():
338 339
            if not entry.ProcessContents():
                sizes_ok = False
340
                print("Entry '%s' size change" % self._node.path)
341
        return sizes_ok
342 343 344 345 346 347

    def WriteSymbols(self):
        """Write symbol values into binary files for access at run time"""
        for entry in self._entries.values():
            entry.WriteSymbols(self)

348
    def BuildSection(self, fd, base_offset):
349
        """Write the section to a file"""
350
        fd.seek(base_offset)
351 352 353
        fd.write(self.GetData())

    def GetData(self):
354
        """Get the contents of the section"""
355
        section_data = tools.GetBytes(self._pad_byte, self._size)
356 357 358

        for entry in self._entries.values():
            data = entry.GetData()
359
            base = self._pad_before + entry.offset - self._skip_at_start
360 361 362 363 364 365 366 367 368 369
            section_data = (section_data[:base] + data +
                            section_data[base + len(data):])
        return section_data

    def LookupSymbol(self, sym_name, optional, msg):
        """Look up a symbol in an ELF file

        Looks up a symbol in an ELF file. Only entry types which come from an
        ELF image can be used by this function.

370
        At present the only entry property supported is offset.
371 372 373 374 375

        Args:
            sym_name: Symbol name in the ELF file to look up in the format
                _binman_<entry>_prop_<property> where <entry> is the name of
                the entry and <property> is the property to find (e.g.
376
                _binman_u_boot_prop_offset). As a special case, you can append
377
                _any to <entry> to have it search for any matching entry. E.g.
378
                _binman_u_boot_any_prop_offset will match entries called u-boot,
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
                u-boot-img and u-boot-nodtb)
            optional: True if the symbol is optional. If False this function
                will raise if the symbol is not found
            msg: Message to display if an error occurs

        Returns:
            Value that should be assigned to that symbol, or None if it was
                optional and not found

        Raises:
            ValueError if the symbol is invalid or not found, or references a
                property which is not supported
        """
        m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
        if not m:
            raise ValueError("%s: Symbol '%s' has invalid format" %
                             (msg, sym_name))
        entry_name, prop_name = m.groups()
        entry_name = entry_name.replace('_', '-')
        entry = self._entries.get(entry_name)
        if not entry:
            if entry_name.endswith('-any'):
                root = entry_name[:-4]
                for name in self._entries:
                    if name.startswith(root):
                        rest = name[len(root):]
                        if rest in ['', '-img', '-nodtb']:
                            entry = self._entries[name]
        if not entry:
            err = ("%s: Entry '%s' not found in list (%s)" %
                   (msg, entry_name, ','.join(self._entries.keys())))
            if optional:
                print('Warning: %s' % err, file=sys.stderr)
                return None
            raise ValueError(err)
414 415
        if prop_name == 'offset':
            return entry.offset
416 417
        elif prop_name == 'image_pos':
            return entry.image_pos
418 419 420 421
        else:
            raise ValueError("%s: No such property '%s'" % (msg, prop_name))

    def GetEntries(self):
422
        """Get the dict of entries in a section
423 424

        Returns:
425
            OrderedDict of entries in a section
426
        """
427
        return self._entries
428

429 430 431 432 433 434 435 436 437 438 439 440
    def GetSize(self):
        """Get the size of a section in bytes

        This is only meaningful if the section has a pre-defined size, or the
        entries within it have been packed, so that the size has been
        calculated.

        Returns:
            Entry size in bytes
        """
        return self._size

441 442 443 444 445 446
    def WriteMap(self, fd, indent):
        """Write a map of the section to a .map file

        Args:
            fd: File to write the map to
        """
447 448
        Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
                           self._size, self._image_pos)
449
        for entry in self._entries.values():
450
            entry.WriteMap(fd, indent + 1)
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470

    def GetContentsByPhandle(self, phandle, source_entry):
        """Get the data contents of an entry specified by a phandle

        This uses a phandle to look up a node and and find the entry
        associated with it. Then it returnst he contents of that entry.

        Args:
            phandle: Phandle to look up (integer)
            source_entry: Entry containing that phandle (used for error
                reporting)

        Returns:
            data from associated entry (as a string), or None if not found
        """
        node = self._node.GetFdt().LookupPhandle(phandle)
        if not node:
            source_entry.Raise("Cannot find node for phandle %d" % phandle)
        for entry in self._entries.values():
            if entry._node == node:
S
Simon Glass 已提交
471
                return entry.GetData()
472
        source_entry.Raise("Cannot find entry for node '%s'" % node.name)
S
Simon Glass 已提交
473 474

    def ExpandSize(self, size):
475 476 477 478 479
        """Change the size of an entry

        Args:
            size: New size for entry
        """
S
Simon Glass 已提交
480 481
        if size != self._size:
            self._size = size
S
Simon Glass 已提交
482 483

    def GetRootSkipAtStart(self):
484 485 486 487 488 489 490 491 492 493 494 495
        """Get the skip-at-start value for the top-level section

        This is used to find out the starting offset for root section that
        contains this section. If this is a top-level section then it returns
        the skip-at-start offset for this section.

        This is used to get the absolute position of section within the image.

        Returns:
            Integer skip-at-start value for the root section containing this
                section
        """
S
Simon Glass 已提交
496 497 498 499
        if self._parent_section:
            return self._parent_section.GetRootSkipAtStart()
        return self._skip_at_start

500 501 502 503 504 505 506 507
    def GetStartOffset(self):
        """Get the start offset for this section

        Returns:
            The first available offset in this section (typically 0)
        """
        return self._skip_at_start

S
Simon Glass 已提交
508
    def GetImageSize(self):
509 510 511 512 513 514
        """Get the size of the image containing this section

        Returns:
            Image size as an integer number of bytes, which may be None if the
                image size is dynamic and its sections have not yet been packed
        """
S
Simon Glass 已提交
515
        return self._image._size
516 517 518 519 520 521 522 523

    def ListEntries(self, entries, indent):
        """Override this method to list all files in the section"""
        Entry.AddEntryInfo(entries, indent, self._name, 'section', self._size,
                           self._image_pos, None, self._offset,
                           self._parent_section)
        for entry in self._entries.values():
            entry.ListEntries(entries, indent + 1)