mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-26 04:53:42 +00:00
e6d85ff9f2
The method of multiplying a character by a number works well for creating a repeated string in Python 2. But in Python 3 we need to use bytes() instead, to avoid unicode problems, since 'bytes' is no-longer just an alias of 'str'. Create a function to handle this detail and call it from the relevant places in binman. Signed-off-by: Simon Glass <sjg@chromium.org>
464 lines
17 KiB
Python
464 lines
17 KiB
Python
# 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
|
|
|
|
import fdt_util
|
|
import re
|
|
import state
|
|
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
|
|
_parent_section: Parent Section object which created this Section
|
|
_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
|
|
effectively changes the place where entry offset 0 starts
|
|
_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
|
|
_sort: True if entries should be sorted by offset, False if they
|
|
must be in-order in the device tree description
|
|
_skip_at_start: Number of bytes before the first entry starts. These
|
|
effectively adjust the starting offset of entries. For example,
|
|
if _pad_before is 16, then the first entry would start at 16.
|
|
An entry with offset = 20 would in fact be written at offset 4
|
|
in the image file.
|
|
_end_4gb: Indicates that the section ends at the 4GB boundary. This is
|
|
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.
|
|
_name_prefix: Prefix to add to the name of all entries within this
|
|
section
|
|
_entries: OrderedDict() of entries
|
|
"""
|
|
def __init__(self, name, parent_section, node, image, test=False):
|
|
global entry
|
|
global Entry
|
|
import entry
|
|
from entry import Entry
|
|
|
|
self._parent_section = parent_section
|
|
self._name = name
|
|
self._node = node
|
|
self._image = image
|
|
self._offset = None
|
|
self._size = None
|
|
self._align_size = None
|
|
self._pad_before = 0
|
|
self._pad_after = 0
|
|
self._pad_byte = 0
|
|
self._sort = False
|
|
self._skip_at_start = None
|
|
self._end_4gb = False
|
|
self._name_prefix = ''
|
|
self._entries = OrderedDict()
|
|
self._image_pos = None
|
|
if not test:
|
|
self._ReadNode()
|
|
self._ReadEntries()
|
|
|
|
def _ReadNode(self):
|
|
"""Read properties from the section node"""
|
|
self._offset = fdt_util.GetInt(self._node, 'offset')
|
|
self._size = fdt_util.GetInt(self._node, 'size')
|
|
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)
|
|
self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
|
|
self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
|
|
self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
|
|
if self._end_4gb:
|
|
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
|
|
self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
|
|
|
|
def _ReadEntries(self):
|
|
for node in self._node.subnodes:
|
|
if node.name == 'hash':
|
|
continue
|
|
entry = Entry.Create(self, node)
|
|
entry.SetPrefix(self._name_prefix)
|
|
self._entries[node.name] = entry
|
|
|
|
def GetFdtSet(self):
|
|
"""Get the set of device tree files used by this image"""
|
|
fdt_set = set()
|
|
for entry in self._entries.values():
|
|
fdt_set.update(entry.GetFdtSet())
|
|
return fdt_set
|
|
|
|
def SetOffset(self, offset):
|
|
self._offset = offset
|
|
|
|
def ExpandEntries(self):
|
|
for entry in self._entries.values():
|
|
entry.ExpandEntries()
|
|
|
|
def AddMissingProperties(self):
|
|
"""Add new properties to the device tree as needed for this entry"""
|
|
for prop in ['offset', 'size', 'image-pos']:
|
|
if not prop in self._node.props:
|
|
state.AddZeroProp(self._node, prop)
|
|
state.CheckAddHashProp(self._node)
|
|
for entry in self._entries.values():
|
|
entry.AddMissingProperties()
|
|
|
|
def SetCalculatedProperties(self):
|
|
state.SetInt(self._node, 'offset', self._offset or 0)
|
|
state.SetInt(self._node, 'size', self._size)
|
|
image_pos = self._image_pos
|
|
if self._parent_section:
|
|
image_pos -= self._parent_section.GetRootSkipAtStart()
|
|
state.SetInt(self._node, 'image-pos', image_pos)
|
|
for entry in self._entries.values():
|
|
entry.SetCalculatedProperties()
|
|
|
|
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
|
|
|
|
def CheckSize(self):
|
|
"""Check that the section contents does not exceed its size, etc."""
|
|
contents_size = 0
|
|
for entry in self._entries.values():
|
|
contents_size = max(contents_size, entry.offset + entry.size)
|
|
|
|
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
|
|
if todo:
|
|
self._Raise('Internal error: Could not complete processing of '
|
|
'contents: remaining %s' % todo)
|
|
return True
|
|
|
|
def _SetEntryOffsetSize(self, name, offset, size):
|
|
"""Set the offset and size of an entry
|
|
|
|
Args:
|
|
name: Entry name to update
|
|
offset: New offset
|
|
size: New size
|
|
"""
|
|
entry = self._entries.get(name)
|
|
if not entry:
|
|
self._Raise("Unable to set offset/size for unknown entry '%s'" %
|
|
name)
|
|
entry.SetOffsetSize(self._skip_at_start + offset, size)
|
|
|
|
def GetEntryOffsets(self):
|
|
"""Handle entries that want to set the offset/size of other entries
|
|
|
|
This calls each entry's GetOffsets() method. If it returns a list
|
|
of entries to update, it updates them.
|
|
"""
|
|
for entry in self._entries.values():
|
|
offset_dict = entry.GetOffsets()
|
|
for name, info in offset_dict.items():
|
|
self._SetEntryOffsetSize(name, *info)
|
|
|
|
def PackEntries(self):
|
|
"""Pack all entries into the section"""
|
|
offset = self._skip_at_start
|
|
for entry in self._entries.values():
|
|
offset = entry.Pack(offset)
|
|
self._size = self.CheckSize()
|
|
|
|
def _SortEntries(self):
|
|
"""Sort entries by offset"""
|
|
entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
|
|
self._entries.clear()
|
|
for entry in entries:
|
|
self._entries[entry._node.name] = entry
|
|
|
|
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)
|
|
|
|
def CheckEntries(self):
|
|
"""Check that entries do not overlap or extend outside the section
|
|
|
|
This also sorts entries, if needed and expands
|
|
"""
|
|
if self._sort:
|
|
self._SortEntries()
|
|
self._ExpandEntries()
|
|
offset = 0
|
|
prev_name = 'None'
|
|
for entry in self._entries.values():
|
|
entry.CheckOffset()
|
|
if (entry.offset < self._skip_at_start or
|
|
entry.offset + entry.size > self._skip_at_start + self._size):
|
|
entry.Raise("Offset %#x (%d) is outside the section starting "
|
|
"at %#x (%d)" %
|
|
(entry.offset, entry.offset, self._skip_at_start,
|
|
self._skip_at_start))
|
|
if entry.offset < offset:
|
|
entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
|
|
"ending at %#x (%d)" %
|
|
(entry.offset, entry.offset, prev_name, offset, offset))
|
|
offset = entry.offset + entry.size
|
|
prev_name = entry.GetPath()
|
|
|
|
def SetImagePos(self, image_pos):
|
|
self._image_pos = image_pos
|
|
for entry in self._entries.values():
|
|
entry.SetImagePos(image_pos)
|
|
|
|
def ProcessEntryContents(self):
|
|
"""Call the ProcessContents() method for each entry
|
|
|
|
This is intended to adjust the contents as needed by the entry type.
|
|
"""
|
|
for entry in self._entries.values():
|
|
entry.ProcessContents()
|
|
|
|
def WriteSymbols(self):
|
|
"""Write symbol values into binary files for access at run time"""
|
|
for entry in self._entries.values():
|
|
entry.WriteSymbols(self)
|
|
|
|
def BuildSection(self, fd, base_offset):
|
|
"""Write the section to a file"""
|
|
fd.seek(base_offset)
|
|
fd.write(self.GetData())
|
|
|
|
def GetData(self):
|
|
"""Get the contents of the section"""
|
|
section_data = tools.GetBytes(self._pad_byte, self._size)
|
|
|
|
for entry in self._entries.values():
|
|
data = entry.GetData()
|
|
base = self._pad_before + entry.offset - self._skip_at_start
|
|
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.
|
|
|
|
At present the only entry property supported is offset.
|
|
|
|
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.
|
|
_binman_u_boot_prop_offset). As a special case, you can append
|
|
_any to <entry> to have it search for any matching entry. E.g.
|
|
_binman_u_boot_any_prop_offset will match entries called u-boot,
|
|
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)
|
|
if prop_name == 'offset':
|
|
return entry.offset
|
|
elif prop_name == 'image_pos':
|
|
return entry.image_pos
|
|
else:
|
|
raise ValueError("%s: No such property '%s'" % (msg, prop_name))
|
|
|
|
def GetEntries(self):
|
|
"""Get the number of entries in a section
|
|
|
|
Returns:
|
|
Number of entries in a section
|
|
"""
|
|
return self._entries
|
|
|
|
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
|
|
|
|
def WriteMap(self, fd, indent):
|
|
"""Write a map of the section to a .map file
|
|
|
|
Args:
|
|
fd: File to write the map to
|
|
"""
|
|
Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
|
|
self._size, self._image_pos)
|
|
for entry in self._entries.values():
|
|
entry.WriteMap(fd, indent + 1)
|
|
|
|
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:
|
|
return entry.GetData()
|
|
source_entry.Raise("Cannot find entry for node '%s'" % node.name)
|
|
|
|
def ExpandSize(self, size):
|
|
if size != self._size:
|
|
self._size = size
|
|
|
|
def GetRootSkipAtStart(self):
|
|
if self._parent_section:
|
|
return self._parent_section.GetRootSkipAtStart()
|
|
return self._skip_at_start
|
|
|
|
def GetImageSize(self):
|
|
return self._image._size
|