binman: Allow entries to expand after packing

Add support for detecting entries that change size after they have already
been packed, and re-running packing when it happens.

This removes the limitation that entry size cannot change after
PackEntries() is called.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2019-07-08 14:25:37 -06:00
parent bf6906bab4
commit c52c9e7da8
12 changed files with 184 additions and 25 deletions

View file

@ -566,7 +566,8 @@ tree. This sets the correct 'offset' and 'size' vaues, for example.
The default implementatoin does nothing. This can be overriden to adjust the The default implementatoin does nothing. This can be overriden to adjust the
contents of an entry in some way. For example, it would be possible to create contents of an entry in some way. For example, it would be possible to create
an entry containing a hash of the contents of some other entries. At this an entry containing a hash of the contents of some other entries. At this
stage the offset and size of entries should not be adjusted. stage the offset and size of entries should not be adjusted unless absolutely
necessary, since it requires a repack (going back to PackEntries()).
10. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. 10. WriteSymbols() - write the value of symbols into the U-Boot SPL binary.
See 'Access to binman entry offsets at run time' below for a description of See 'Access to binman entry offsets at run time' below for a description of

View file

@ -45,6 +45,8 @@ class Section(object):
_name_prefix: Prefix to add to the name of all entries within this _name_prefix: Prefix to add to the name of all entries within this
section section
_entries: OrderedDict() of entries _entries: OrderedDict() of entries
_orig_offset: Original offset value read from node
_orig_size: Original size value read from node
""" """
def __init__(self, name, parent_section, node, image, test=False): def __init__(self, name, parent_section, node, image, test=False):
global entry global entry
@ -76,6 +78,8 @@ class Section(object):
"""Read properties from the section node""" """Read properties from the section node"""
self._offset = fdt_util.GetInt(self._node, 'offset') self._offset = fdt_util.GetInt(self._node, 'offset')
self._size = fdt_util.GetInt(self._node, 'size') self._size = fdt_util.GetInt(self._node, 'size')
self._orig_offset = self._offset
self._orig_size = self._size
self._align_size = fdt_util.GetInt(self._node, 'align-size') self._align_size = fdt_util.GetInt(self._node, 'align-size')
if tools.NotPowerOfTwo(self._align_size): if tools.NotPowerOfTwo(self._align_size):
self._Raise("Alignment size %s must be a power of two" % self._Raise("Alignment size %s must be a power of two" %
@ -257,6 +261,13 @@ class Section(object):
for name, info in offset_dict.items(): for name, info in offset_dict.items():
self._SetEntryOffsetSize(name, *info) self._SetEntryOffsetSize(name, *info)
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()
def PackEntries(self): def PackEntries(self):
"""Pack all entries into the section""" """Pack all entries into the section"""
offset = self._skip_at_start offset = self._skip_at_start
@ -325,6 +336,7 @@ class Section(object):
for entry in self._entries.values(): for entry in self._entries.values():
if not entry.ProcessContents(): if not entry.ProcessContents():
sizes_ok = False sizes_ok = False
print("Entry '%s' size change" % self._node.path)
return sizes_ok return sizes_ok
def WriteSymbols(self): def WriteSymbols(self):

View file

@ -170,21 +170,42 @@ def Binman(args):
# completed and written, but that does not seem important. # completed and written, but that does not seem important.
image.GetEntryContents() image.GetEntryContents()
image.GetEntryOffsets() image.GetEntryOffsets()
try:
image.PackEntries() # We need to pack the entries to figure out where everything
image.CheckSize() # should be placed. This sets the offset/size of each entry.
image.CheckEntries() # However, after packing we call ProcessEntryContents() which
except Exception as e: # may result in an entry changing size. In that case we need to
if args.map: # do another pass. Since the device tree often contains the
fname = image.WriteMap() # final offset/size information we try to make space for this in
print("Wrote map file '%s' to show errors" % fname) # AddMissingProperties() above. However, if the device is
raise # compressed we cannot know this compressed size in advance,
image.SetImagePos() # since changing an offset from 0x100 to 0x104 (for example) can
if args.update_fdt: # alter the compressed size of the device tree. So we need a
image.SetCalculatedProperties() # third pass for this.
for dtb_item in state.GetFdts(): passes = 3
dtb_item.Sync() for pack_pass in range(passes):
image.ProcessEntryContents() try:
image.PackEntries()
image.CheckSize()
image.CheckEntries()
except Exception as e:
if args.map:
fname = image.WriteMap()
print("Wrote map file '%s' to show errors" % fname)
raise
image.SetImagePos()
if args.update_fdt:
image.SetCalculatedProperties()
for dtb_item in state.GetFdts():
dtb_item.Sync()
sizes_ok = image.ProcessEntryContents()
if sizes_ok:
break
image.ResetForPack()
if not sizes_ok:
image.Raise('Entries expanded after packing (tried %s passes)' %
passes)
image.WriteSymbols() image.WriteSymbols()
image.BuildImage() image.BuildImage()
if args.map: if args.map:

View file

@ -61,6 +61,8 @@ class Entry(object):
pad_after: Number of pad bytes after the contents, 0 if none pad_after: Number of pad bytes after the contents, 0 if none
data: Contents of entry (string of bytes) data: Contents of entry (string of bytes)
compress: Compression algoithm used (e.g. 'lz4'), 'none' if none compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
orig_offset: Original offset value read from node
orig_size: Original size value read from node
""" """
def __init__(self, section, etype, node, read_node=True, name_prefix=''): def __init__(self, section, etype, node, read_node=True, name_prefix=''):
self.section = section self.section = section
@ -153,6 +155,9 @@ class Entry(object):
self.Raise("Please use 'offset' instead of 'pos'") self.Raise("Please use 'offset' instead of 'pos'")
self.offset = fdt_util.GetInt(self._node, 'offset') self.offset = fdt_util.GetInt(self._node, 'offset')
self.size = fdt_util.GetInt(self._node, 'size') self.size = fdt_util.GetInt(self._node, 'size')
self.orig_offset = self.offset
self.orig_size = self.size
self.align = fdt_util.GetInt(self._node, 'align') self.align = fdt_util.GetInt(self._node, 'align')
if tools.NotPowerOfTwo(self.align): if tools.NotPowerOfTwo(self.align):
raise ValueError("Node '%s': Alignment %s must be a power of two" % raise ValueError("Node '%s': Alignment %s must be a power of two" %
@ -255,9 +260,16 @@ class Entry(object):
ValueError if the new data size is not the same as the old ValueError if the new data size is not the same as the old
""" """
size_ok = True size_ok = True
if len(data) != self.contents_size: new_size = len(data)
if state.AllowEntryExpansion():
if new_size > self.contents_size:
print("Entry '%s' size change from %#x to %#x" % (
self._node.path, self.contents_size, new_size))
# self.data will indicate the new size needed
size_ok = False
elif new_size != self.contents_size:
self.Raise('Cannot update entry size from %d to %d' % self.Raise('Cannot update entry size from %d to %d' %
(self.contents_size, len(data))) (self.contents_size, new_size))
self.SetContents(data) self.SetContents(data)
return size_ok return size_ok
@ -271,6 +283,11 @@ class Entry(object):
# No contents by default: subclasses can implement this # No contents by default: subclasses can implement this
return True return True
def ResetForPack(self):
"""Reset offset/size fields so that packing can be done again"""
self.offset = self.orig_offset
self.size = self.orig_size
def Pack(self, offset): def Pack(self, offset):
"""Figure out how to pack the entry into the section """Figure out how to pack the entry into the section

View file

@ -50,6 +50,8 @@ class Entry__testing(Entry):
'bad-update-contents') 'bad-update-contents')
self.return_contents_once = fdt_util.GetBool(self._node, self.return_contents_once = fdt_util.GetBool(self._node,
'return-contents-once') 'return-contents-once')
self.bad_update_contents_twice = fdt_util.GetBool(self._node,
'bad-update-contents-twice')
# Set to True when the entry is ready to process the FDT. # Set to True when the entry is ready to process the FDT.
self.process_fdt_ready = False self.process_fdt_ready = False
@ -71,11 +73,12 @@ class Entry__testing(Entry):
if self.force_bad_datatype: if self.force_bad_datatype:
self.GetEntryArgsOrProps([EntryArg('test-bad-datatype-arg', bool)]) self.GetEntryArgsOrProps([EntryArg('test-bad-datatype-arg', bool)])
self.return_contents = True self.return_contents = True
self.contents = b'a'
def ObtainContents(self): def ObtainContents(self):
if self.return_unknown_contents or not self.return_contents: if self.return_unknown_contents or not self.return_contents:
return False return False
self.data = b'a' self.data = self.contents
self.contents_size = len(self.data) self.contents_size = len(self.data)
if self.return_contents_once: if self.return_contents_once:
self.return_contents = False self.return_contents = False
@ -90,7 +93,11 @@ class Entry__testing(Entry):
if self.bad_update_contents: if self.bad_update_contents:
# Request to update the contents with something larger, to cause a # Request to update the contents with something larger, to cause a
# failure. # failure.
return self.ProcessContentsUpdate('aa') if self.bad_update_contents_twice:
self.contents += b'a'
else:
self.contents = b'aa'
return self.ProcessContentsUpdate(self.contents)
return True return True
def ProcessFdt(self, fdt): def ProcessFdt(self, fdt):

View file

@ -64,6 +64,11 @@ class Entry_section(Entry):
self._section.GetEntryOffsets() self._section.GetEntryOffsets()
return {} return {}
def ResetForPack(self):
"""Reset offset/size fields so that packing can be done again"""
self._section.ResetForPack()
Entry.ResetForPack(self)
def Pack(self, offset): def Pack(self, offset):
"""Pack all entries into the section""" """Pack all entries into the section"""
self._section.PackEntries() self._section.PackEntries()

View file

@ -49,7 +49,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob):
def ProcessContents(self): def ProcessContents(self):
# If the image does not need microcode, there is nothing to do # If the image does not need microcode, there is nothing to do
if not self.target_offset: if not self.target_offset:
return return True
# Get the offset of the microcode # Get the offset of the microcode
ucode_entry = self.section.FindEntryType('u-boot-ucode') ucode_entry = self.section.FindEntryType('u-boot-ucode')

View file

@ -1220,10 +1220,14 @@ class TestFunctional(unittest.TestCase):
def testBadChangeSize(self): def testBadChangeSize(self):
"""Test that trying to change the size of an entry fails""" """Test that trying to change the size of an entry fails"""
with self.assertRaises(ValueError) as e: try:
self._DoReadFile('059_change_size.dts', True) state.SetAllowEntryExpansion(False)
self.assertIn("Node '/binman/_testing': Cannot update entry size from " with self.assertRaises(ValueError) as e:
'1 to 2', str(e.exception)) self._DoReadFile('059_change_size.dts', True)
self.assertIn("Node '/binman/_testing': Cannot update entry size from 1 to 2",
str(e.exception))
finally:
state.SetAllowEntryExpansion(True)
def testUpdateFdt(self): def testUpdateFdt(self):
"""Test that we can update the device tree with offset/size info""" """Test that we can update the device tree with offset/size info"""
@ -2117,6 +2121,27 @@ class TestFunctional(unittest.TestCase):
self.assertIn("Invalid location 'None', expected 'start' or 'end'", self.assertIn("Invalid location 'None', expected 'start' or 'end'",
str(e.exception)) str(e.exception))
def testEntryExpand(self):
"""Test expanding an entry after it is packed"""
data = self._DoReadFile('121_entry_expand.dts')
self.assertEqual(b'aa', data[:2])
self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
self.assertEqual(b'aa', data[-2:])
def testEntryExpandBad(self):
"""Test expanding an entry after it is packed, twice"""
with self.assertRaises(ValueError) as e:
self._DoReadFile('122_entry_expand_twice.dts')
self.assertIn("Image '/binman': Entries expanded after packing",
str(e.exception))
def testEntryExpandSection(self):
"""Test expanding an entry within a section after it is packed"""
data = self._DoReadFile('123_entry_expand_section.dts')
self.assertEqual(b'aa', data[:2])
self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
self.assertEqual(b'aa', data[-2:])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -55,6 +55,10 @@ class Image:
self._filename = filename self._filename = filename
self._section = bsection.Section('main-section', None, self._node, self) self._section = bsection.Section('main-section', None, self._node, self)
def Raise(self, msg):
"""Convenience function to raise an error referencing an image"""
raise ValueError("Image '%s': %s" % (self._node.path, msg))
def GetFdtSet(self): def GetFdtSet(self):
"""Get the set of device tree files used by this image""" """Get the set of device tree files used by this image"""
return self._section.GetFdtSet() return self._section.GetFdtSet()
@ -100,6 +104,10 @@ class Image:
""" """
self._section.GetEntryOffsets() self._section.GetEntryOffsets()
def ResetForPack(self):
"""Reset offset/size fields so that packing can be done again"""
self._section.ResetForPack()
def PackEntries(self): def PackEntries(self):
"""Pack all entries into the image""" """Pack all entries into the image"""
self._section.PackEntries() self._section.PackEntries()

View file

@ -0,0 +1,20 @@
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
_testing {
bad-update-contents;
};
u-boot {
};
_testing2 {
type = "_testing";
bad-update-contents;
};
};
};

View file

@ -0,0 +1,21 @@
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
_testing {
bad-update-contents;
bad-update-contents-twice;
};
u-boot {
};
_testing2 {
type = "_testing";
bad-update-contents;
};
};
};

View file

@ -0,0 +1,22 @@
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
_testing {
bad-update-contents;
};
u-boot {
};
section {
_testing2 {
type = "_testing";
bad-update-contents;
};
};
};
};