binman: Refactor much of the image code into 'section'
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>
This commit is contained in:
parent
dd57c13bbc
commit
8f1da50ccc
302
tools/binman/bsection.py
Normal file
302
tools/binman/bsection.py
Normal file
@ -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
|
@ -413,7 +413,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
self.assertEqual(0, retcode)
|
self.assertEqual(0, retcode)
|
||||||
self.assertIn('image', control.images)
|
self.assertIn('image', control.images)
|
||||||
image = control.images['image']
|
image = control.images['image']
|
||||||
entries = image._entries
|
entries = image.GetEntries()
|
||||||
self.assertEqual(5, len(entries))
|
self.assertEqual(5, len(entries))
|
||||||
|
|
||||||
# First u-boot
|
# First u-boot
|
||||||
@ -456,7 +456,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
self.assertEqual(0, retcode)
|
self.assertEqual(0, retcode)
|
||||||
self.assertIn('image', control.images)
|
self.assertIn('image', control.images)
|
||||||
image = control.images['image']
|
image = control.images['image']
|
||||||
entries = image._entries
|
entries = image.GetEntries()
|
||||||
self.assertEqual(5, len(entries))
|
self.assertEqual(5, len(entries))
|
||||||
|
|
||||||
# First u-boot with padding before and after
|
# First u-boot with padding before and after
|
||||||
@ -540,7 +540,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
"""Test that entries which overflow the image size are detected"""
|
"""Test that entries which overflow the image size are detected"""
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoTestFile('16_pack_image_overflow.dts')
|
self._DoTestFile('16_pack_image_overflow.dts')
|
||||||
self.assertIn("Image '/binman': contents size 0x4 (4) exceeds image "
|
self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
|
||||||
"size 0x3 (3)", str(e.exception))
|
"size 0x3 (3)", str(e.exception))
|
||||||
|
|
||||||
def testPackImageSize(self):
|
def testPackImageSize(self):
|
||||||
@ -563,14 +563,14 @@ class TestFunctional(unittest.TestCase):
|
|||||||
"""Test that invalid image alignment is detected"""
|
"""Test that invalid image alignment is detected"""
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoTestFile('19_pack_inv_image_align.dts')
|
self._DoTestFile('19_pack_inv_image_align.dts')
|
||||||
self.assertIn("Image '/binman': Size 0x7 (7) does not match "
|
self.assertIn("Section '/binman': Size 0x7 (7) does not match "
|
||||||
"align-size 0x8 (8)", str(e.exception))
|
"align-size 0x8 (8)", str(e.exception))
|
||||||
|
|
||||||
def testPackAlignPowerOf2(self):
|
def testPackAlignPowerOf2(self):
|
||||||
"""Test that invalid image alignment is detected"""
|
"""Test that invalid image alignment is detected"""
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoTestFile('20_pack_inv_image_align_power2.dts')
|
self._DoTestFile('20_pack_inv_image_align_power2.dts')
|
||||||
self.assertIn("Image '/binman': Alignment size 131 must be a power of "
|
self.assertIn("Section '/binman': Alignment size 131 must be a power of "
|
||||||
"two", str(e.exception))
|
"two", str(e.exception))
|
||||||
|
|
||||||
def testImagePadByte(self):
|
def testImagePadByte(self):
|
||||||
@ -620,7 +620,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
"""Test that the end-at-4gb property requires a size property"""
|
"""Test that the end-at-4gb property requires a size property"""
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoTestFile('27_pack_4gb_no_size.dts')
|
self._DoTestFile('27_pack_4gb_no_size.dts')
|
||||||
self.assertIn("Image '/binman': Image size must be provided when "
|
self.assertIn("Section '/binman': Section size must be provided when "
|
||||||
"using end-at-4gb", str(e.exception))
|
"using end-at-4gb", str(e.exception))
|
||||||
|
|
||||||
def testPackX86RomOutside(self):
|
def testPackX86RomOutside(self):
|
||||||
@ -628,7 +628,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoTestFile('28_pack_4gb_outside.dts')
|
self._DoTestFile('28_pack_4gb_outside.dts')
|
||||||
self.assertIn("Node '/binman/u-boot': Position 0x0 (0) is outside "
|
self.assertIn("Node '/binman/u-boot': Position 0x0 (0) is outside "
|
||||||
"the image starting at 0xffffffe0 (4294967264)",
|
"the section starting at 0xffffffe0 (4294967264)",
|
||||||
str(e.exception))
|
str(e.exception))
|
||||||
|
|
||||||
def testPackX86Rom(self):
|
def testPackX86Rom(self):
|
||||||
@ -823,7 +823,7 @@ class TestFunctional(unittest.TestCase):
|
|||||||
"""Test that microcode must be placed within the image"""
|
"""Test that microcode must be placed within the image"""
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
self._DoReadFile('41_unknown_pos_size.dts', True)
|
self._DoReadFile('41_unknown_pos_size.dts', True)
|
||||||
self.assertIn("Image '/binman': Unable to set pos/size for unknown "
|
self.assertIn("Section '/binman': Unable to set pos/size for unknown "
|
||||||
"entry 'invalid-entry'", str(e.exception))
|
"entry 'invalid-entry'", str(e.exception))
|
||||||
|
|
||||||
def testPackFsp(self):
|
def testPackFsp(self):
|
||||||
|
@ -13,6 +13,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import fdt_util
|
import fdt_util
|
||||||
|
import bsection
|
||||||
import tools
|
import tools
|
||||||
|
|
||||||
class Image:
|
class Image:
|
||||||
@ -27,158 +28,31 @@ class Image:
|
|||||||
_node: Node object that contains the image definition in device tree
|
_node: Node object that contains the image definition in device tree
|
||||||
_name: Image name
|
_name: Image name
|
||||||
_size: Image size in bytes, or None if not known yet
|
_size: Image size in bytes, or None if not known yet
|
||||||
_align_size: Image 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 image where there is no entry
|
|
||||||
_filename: Output filename for image
|
_filename: Output filename for image
|
||||||
_sort: True if entries should be sorted by position, False if they
|
_sections: Sections present in this image (may be one or more)
|
||||||
must be in-order in the device tree description
|
|
||||||
_skip_at_start: Number of bytes before the first entry starts. These
|
|
||||||
effecively 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 image 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):
|
def __init__(self, name, node, test=False):
|
||||||
global entry
|
|
||||||
global Entry
|
|
||||||
import entry
|
|
||||||
from entry import Entry
|
|
||||||
|
|
||||||
self._node = node
|
self._node = node
|
||||||
self._name = name
|
self._name = name
|
||||||
self._size = None
|
self._size = None
|
||||||
self._align_size = None
|
|
||||||
self._pad_before = 0
|
|
||||||
self._pad_after = 0
|
|
||||||
self._pad_byte = 0
|
|
||||||
self._filename = '%s.bin' % self._name
|
self._filename = '%s.bin' % self._name
|
||||||
self._sort = False
|
if test:
|
||||||
self._skip_at_start = 0
|
self._section = bsection.Section('main-section', self._node, True)
|
||||||
self._end_4gb = False
|
else:
|
||||||
self._entries = OrderedDict()
|
|
||||||
|
|
||||||
if not test:
|
|
||||||
self._ReadNode()
|
self._ReadNode()
|
||||||
self._ReadEntries()
|
|
||||||
|
|
||||||
def _ReadNode(self):
|
def _ReadNode(self):
|
||||||
"""Read properties from the image node"""
|
"""Read properties from the image node"""
|
||||||
self._size = fdt_util.GetInt(self._node, 'size')
|
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)
|
|
||||||
filename = fdt_util.GetString(self._node, 'filename')
|
filename = fdt_util.GetString(self._node, 'filename')
|
||||||
if filename:
|
if filename:
|
||||||
self._filename = filename
|
self._filename = filename
|
||||||
self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
|
self._section = bsection.Section('main-section', self._node)
|
||||||
self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
|
|
||||||
if self._end_4gb and not self._size:
|
|
||||||
self._Raise("Image size must be provided when using end-at-4gb")
|
|
||||||
if self._end_4gb:
|
|
||||||
self._skip_at_start = 0x100000000 - self._size
|
|
||||||
|
|
||||||
def CheckSize(self):
|
|
||||||
"""Check that the image 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 image 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))
|
|
||||||
|
|
||||||
def _Raise(self, msg):
|
|
||||||
"""Raises an error for this image
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg: Error message to use in the raise string
|
|
||||||
Raises:
|
|
||||||
ValueError()
|
|
||||||
"""
|
|
||||||
raise ValueError("Image '%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 _ReadEntries(self):
|
|
||||||
for node in self._node.subnodes:
|
|
||||||
self._entries[node.name] = Entry.Create(self, node)
|
|
||||||
|
|
||||||
def FindEntryType(self, etype):
|
|
||||||
"""Find an entry type in the image
|
|
||||||
|
|
||||||
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):
|
def GetEntryContents(self):
|
||||||
"""Call ObtainContents() for each entry
|
"""Call ObtainContents() for the section
|
||||||
|
|
||||||
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()
|
self._section.GetEntryContents()
|
||||||
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):
|
def GetEntryPositions(self):
|
||||||
"""Handle entries that want to set the position/size of other entries
|
"""Handle entries that want to set the position/size of other entries
|
||||||
@ -186,119 +60,36 @@ class Image:
|
|||||||
This calls each entry's GetPositions() method. If it returns a list
|
This calls each entry's GetPositions() method. If it returns a list
|
||||||
of entries to update, it updates them.
|
of entries to update, it updates them.
|
||||||
"""
|
"""
|
||||||
for entry in self._entries.values():
|
self._section.GetEntryPositions()
|
||||||
pos_dict = entry.GetPositions()
|
|
||||||
for name, info in pos_dict.iteritems():
|
|
||||||
self._SetEntryPosSize(name, *info)
|
|
||||||
|
|
||||||
def PackEntries(self):
|
def PackEntries(self):
|
||||||
"""Pack all entries into the image"""
|
"""Pack all entries into the image"""
|
||||||
pos = self._skip_at_start
|
self._section.PackEntries()
|
||||||
for entry in self._entries.values():
|
|
||||||
pos = entry.Pack(pos)
|
|
||||||
|
|
||||||
def _SortEntries(self):
|
def CheckSize(self):
|
||||||
"""Sort entries by position"""
|
"""Check that the image contents does not exceed its size, etc."""
|
||||||
entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
|
self._size = self._section.CheckSize()
|
||||||
self._entries.clear()
|
|
||||||
for entry in entries:
|
|
||||||
self._entries[entry._node.name] = entry
|
|
||||||
|
|
||||||
def CheckEntries(self):
|
def CheckEntries(self):
|
||||||
"""Check that entries do not overlap or extend outside the image"""
|
"""Check that entries do not overlap or extend outside the image"""
|
||||||
if self._sort:
|
self._section.CheckEntries()
|
||||||
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 image 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):
|
def ProcessEntryContents(self):
|
||||||
"""Call the ProcessContents() method for each entry
|
"""Call the ProcessContents() method for each entry
|
||||||
|
|
||||||
This is intended to adjust the contents as needed by the entry type.
|
This is intended to adjust the contents as needed by the entry type.
|
||||||
"""
|
"""
|
||||||
for entry in self._entries.values():
|
self._section.ProcessEntryContents()
|
||||||
entry.ProcessContents()
|
|
||||||
|
|
||||||
def WriteSymbols(self):
|
def WriteSymbols(self):
|
||||||
"""Write symbol values into binary files for access at run time"""
|
"""Write symbol values into binary files for access at run time"""
|
||||||
for entry in self._entries.values():
|
self._section.WriteSymbols()
|
||||||
entry.WriteSymbols(self)
|
|
||||||
|
|
||||||
def BuildImage(self):
|
def BuildImage(self):
|
||||||
"""Write the image to a file"""
|
"""Write the image to a file"""
|
||||||
fname = tools.GetOutputFilename(self._filename)
|
fname = tools.GetOutputFilename(self._filename)
|
||||||
with open(fname, 'wb') as fd:
|
with open(fname, 'wb') as fd:
|
||||||
fd.write(chr(self._pad_byte) * self._size)
|
self._section.BuildSection(fd, 0)
|
||||||
|
|
||||||
for entry in self._entries.values():
|
def GetEntries(self):
|
||||||
data = entry.GetData()
|
return self._section.GetEntries()
|
||||||
fd.seek(self._pad_before + entry.pos - self._skip_at_start)
|
|
||||||
fd.write(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))
|
|
||||||
|
@ -12,25 +12,28 @@ from elf_test import capture_sys_output
|
|||||||
class TestImage(unittest.TestCase):
|
class TestImage(unittest.TestCase):
|
||||||
def testInvalidFormat(self):
|
def testInvalidFormat(self):
|
||||||
image = Image('name', 'node', test=True)
|
image = Image('name', 'node', test=True)
|
||||||
|
section = image._section
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
image.LookupSymbol('_binman_something_prop_', False, 'msg')
|
section.LookupSymbol('_binman_something_prop_', False, 'msg')
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"msg: Symbol '_binman_something_prop_' has invalid format",
|
"msg: Symbol '_binman_something_prop_' has invalid format",
|
||||||
str(e.exception))
|
str(e.exception))
|
||||||
|
|
||||||
def testMissingSymbol(self):
|
def testMissingSymbol(self):
|
||||||
image = Image('name', 'node', test=True)
|
image = Image('name', 'node', test=True)
|
||||||
image._entries = {}
|
section = image._section
|
||||||
|
section._entries = {}
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
image.LookupSymbol('_binman_type_prop_pname', False, 'msg')
|
section.LookupSymbol('_binman_type_prop_pname', False, 'msg')
|
||||||
self.assertIn("msg: Entry 'type' not found in list ()",
|
self.assertIn("msg: Entry 'type' not found in list ()",
|
||||||
str(e.exception))
|
str(e.exception))
|
||||||
|
|
||||||
def testMissingSymbolOptional(self):
|
def testMissingSymbolOptional(self):
|
||||||
image = Image('name', 'node', test=True)
|
image = Image('name', 'node', test=True)
|
||||||
image._entries = {}
|
section = image._section
|
||||||
|
section._entries = {}
|
||||||
with capture_sys_output() as (stdout, stderr):
|
with capture_sys_output() as (stdout, stderr):
|
||||||
val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg')
|
val = section.LookupSymbol('_binman_type_prop_pname', True, 'msg')
|
||||||
self.assertEqual(val, None)
|
self.assertEqual(val, None)
|
||||||
self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n",
|
self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n",
|
||||||
stderr.getvalue())
|
stderr.getvalue())
|
||||||
@ -38,7 +41,8 @@ class TestImage(unittest.TestCase):
|
|||||||
|
|
||||||
def testBadProperty(self):
|
def testBadProperty(self):
|
||||||
image = Image('name', 'node', test=True)
|
image = Image('name', 'node', test=True)
|
||||||
image._entries = {'u-boot': 1}
|
section = image._section
|
||||||
|
section._entries = {'u-boot': 1}
|
||||||
with self.assertRaises(ValueError) as e:
|
with self.assertRaises(ValueError) as e:
|
||||||
image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
|
section.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
|
||||||
self.assertIn("msg: No such property 'bad", str(e.exception))
|
self.assertIn("msg: No such property 'bad", str(e.exception))
|
||||||
|
Loading…
Reference in New Issue
Block a user