We want to support multiple sections within a single image. To do this, move most of the Image class implementation into a new Section class. An Image contains only a single Section, but at some point we will support a new 'section' entry, thus allowing Sections within Sections. Use the name 'bsection' for the module so we can use 'section' for the etype module. Signed-off-by: Simon Glass <sjg@chromium.org>lime2-spi
parent
dd57c13bbc
commit
8f1da50ccc
@ -0,0 +1,302 @@ |
||||
# 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 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 |
||||
_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 position 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 position, 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 position of entries. For example, |
||||
if _pad_before is 16, then the first entry would start at 16. |
||||
An entry with pos = 20 would in fact be written at position 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 positions such that a |
||||
memory address (like 0xff800000) is the first entry position. |
||||
This causes _skip_at_start to be set to the starting memory |
||||
address. |
||||
_entries: OrderedDict() of entries |
||||
""" |
||||
def __init__(self, name, node, test=False): |
||||
global entry |
||||
global Entry |
||||
import entry |
||||
from entry import Entry |
||||
|
||||
self._node = node |
||||
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 = 0 |
||||
self._end_4gb = False |
||||
self._entries = OrderedDict() |
||||
if not test: |
||||
self._ReadNode() |
||||
self._ReadEntries() |
||||
|
||||
def _ReadNode(self): |
||||
"""Read properties from the section node""" |
||||
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-pos') |
||||
self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') |
||||
if self._end_4gb and not self._size: |
||||
self._Raise("Section size must be provided when using end-at-4gb") |
||||
if self._end_4gb: |
||||
self._skip_at_start = 0x100000000 - self._size |
||||
|
||||
def _ReadEntries(self): |
||||
for node in self._node.subnodes: |
||||
self._entries[node.name] = Entry.Create(self, node) |
||||
|
||||
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.pos + 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 |
||||
|
||||
def _SetEntryPosSize(self, name, pos, size): |
||||
"""Set the position and size of an entry |
||||
|
||||
Args: |
||||
name: Entry name to update |
||||
pos: New position |
||||
size: New size |
||||
""" |
||||
entry = self._entries.get(name) |
||||
if not entry: |
||||
self._Raise("Unable to set pos/size for unknown entry '%s'" % name) |
||||
entry.SetPositionSize(self._skip_at_start + pos, size) |
||||
|
||||
def GetEntryPositions(self): |
||||
"""Handle entries that want to set the position/size of other entries |
||||
|
||||
This calls each entry's GetPositions() method. If it returns a list |
||||
of entries to update, it updates them. |
||||
""" |
||||
for entry in self._entries.values(): |
||||
pos_dict = entry.GetPositions() |
||||
for name, info in pos_dict.iteritems(): |
||||
self._SetEntryPosSize(name, *info) |
||||
|
||||
def PackEntries(self): |
||||
"""Pack all entries into the section""" |
||||
pos = self._skip_at_start |
||||
for entry in self._entries.values(): |
||||
pos = entry.Pack(pos) |
||||
|
||||
def _SortEntries(self): |
||||
"""Sort entries by position""" |
||||
entries = sorted(self._entries.values(), key=lambda entry: entry.pos) |
||||
self._entries.clear() |
||||
for entry in entries: |
||||
self._entries[entry._node.name] = entry |
||||
|
||||
def CheckEntries(self): |
||||
"""Check that entries do not overlap or extend outside the section""" |
||||
if self._sort: |
||||
self._SortEntries() |
||||
pos = 0 |
||||
prev_name = 'None' |
||||
for entry in self._entries.values(): |
||||
if (entry.pos < self._skip_at_start or |
||||
entry.pos >= self._skip_at_start + self._size): |
||||
entry.Raise("Position %#x (%d) is outside the section starting " |
||||
"at %#x (%d)" % |
||||
(entry.pos, entry.pos, self._skip_at_start, |
||||
self._skip_at_start)) |
||||
if entry.pos < pos: |
||||
entry.Raise("Position %#x (%d) overlaps with previous entry '%s' " |
||||
"ending at %#x (%d)" % |
||||
(entry.pos, entry.pos, prev_name, pos, pos)) |
||||
pos = entry.pos + entry.size |
||||
prev_name = entry.GetPath() |
||||
|
||||
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_pos): |
||||
"""Write the section to a file""" |
||||
fd.seek(base_pos) |
||||
fd.write(self.GetData()) |
||||
|
||||
def GetData(self): |
||||
"""Write the section to a file""" |
||||
section_data = chr(self._pad_byte) * self._size |
||||
|
||||
for entry in self._entries.values(): |
||||
data = entry.GetData() |
||||
base = self._pad_before + entry.pos - 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 pos. |
||||
|
||||
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_pos). 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_pos 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 == 'pos': |
||||
return entry.pos |
||||
else: |
||||
raise ValueError("%s: No such property '%s'" % (msg, prop_name)) |
||||
|
||||
def GetEntries(self): |
||||
return self._entries |
Loading…
Reference in new issue