tpm fixes for coral
binman fixes support for symbols in sub-sections
support for additional cros_ec commands
various minor fixes / tweaks
This commit is contained in:
Tom Rini
2021-01-31 08:49:53 -05:00
62 changed files with 1487 additions and 137 deletions

View File

@@ -1050,10 +1050,9 @@ Some ideas:
- Allow easy building of images by specifying just the board name
- Support building an image for a board (-b) more completely, with a
configurable build directory
- Support adding FITs to an image
- Support for ARM Trusted Firmware (ATF)
- Detect invalid properties in nodes
- Sort the fdtmap by offset
- Output temporary files to a different directory
--
Simon Glass <sjg@chromium.org>

View File

@@ -244,7 +244,8 @@ def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
if not os.path.exists(fname):
os.makedirs(fname)
fname = os.path.join(fname, 'root')
tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
tout.Notice("Write entry '%s' size %x to '%s'" %
(entry.GetPath(), len(data), fname))
tools.WriteFile(fname, data)
return einfos

View File

@@ -132,7 +132,8 @@ def LookupAndWriteSymbols(elf_fname, entry, section):
(msg, sym.size))
# Look up the symbol in our entry tables.
value = section.LookupSymbol(name, sym.weak, msg, base.address)
value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
base.address)
if value is None:
value = -1
pack_string = pack_string.lower()

View File

@@ -45,10 +45,12 @@ class FakeSection:
def GetPath(self):
return 'section_path'
def LookupSymbol(self, name, weak, msg, base_addr):
def LookupImageSymbol(self, name, weak, msg, base_addr):
"""Fake implementation which returns the same value for all symbols"""
return self.sym_value
def GetImage(self):
return self
def BuildElfTestFiles(target_dir):
"""Build ELF files used for testing in binman

View File

@@ -434,8 +434,7 @@ class Entry(object):
missing.append(prop.name)
values.append(value)
if missing:
self.Raise('Missing required properties/entry args: %s' %
(', '.join(missing)))
self.GetImage().MissingArgs(self, missing)
return values
def GetPath(self):

View File

@@ -66,3 +66,7 @@ class Entry_blob(Entry):
def GetDefaultFilename(self):
return self._filename
def ProcessContents(self):
# The blob may have changed due to WriteSymbols()
return self.ProcessContentsUpdate(self.data)

View File

@@ -22,6 +22,7 @@ class Entry_files(Entry_section):
- files-compress: Compression algorithm to use:
none: No compression
lz4: Use lz4 compression (via 'lz4' command-line utility)
- files-align: Align each file to the given alignment
This entry reads a number of files and places each in a separate sub-entry
within this entry. To access these you need to enable device-tree updates
@@ -38,6 +39,7 @@ class Entry_files(Entry_section):
self.Raise("Missing 'pattern' property")
self._files_compress = fdt_util.GetString(self._node, 'files-compress',
'none')
self._files_align = fdt_util.GetInt(self._node, 'files-align');
self._require_matches = fdt_util.GetBool(self._node,
'require-matches')
@@ -55,6 +57,8 @@ class Entry_files(Entry_section):
state.AddString(subnode, 'type', 'blob')
state.AddString(subnode, 'filename', fname)
state.AddString(subnode, 'compress', self._files_compress)
if self._files_align:
state.AddInt(subnode, 'align', self._files_align)
# Read entries again, now that we have some
self._ReadEntries()

View File

@@ -385,7 +385,7 @@ class Entry_section(Entry):
return entry.GetData()
source_entry.Raise("Cannot find entry for node '%s'" % node.name)
def LookupSymbol(self, sym_name, optional, msg, base_addr):
def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None):
"""Look up a symbol in an ELF file
Looks up a symbol in an ELF file. Only entry types which come from an
@@ -428,18 +428,20 @@ class Entry_section(Entry):
(msg, sym_name))
entry_name, prop_name = m.groups()
entry_name = entry_name.replace('_', '-')
entry = self._entries.get(entry_name)
if not entries:
entries = self._entries
entry = entries.get(entry_name)
if not entry:
if entry_name.endswith('-any'):
root = entry_name[:-4]
for name in self._entries:
for name in entries:
if name.startswith(root):
rest = name[len(root):]
if rest in ['', '-img', '-nodtb']:
entry = self._entries[name]
entry = entries[name]
if not entry:
err = ("%s: Entry '%s' not found in list (%s)" %
(msg, entry_name, ','.join(self._entries.keys())))
(msg, entry_name, ','.join(entries.keys())))
if optional:
print('Warning: %s' % err, file=sys.stderr)
return None
@@ -603,10 +605,12 @@ class Entry_section(Entry):
def ReadData(self, decomp=True):
tout.Info("ReadData path='%s'" % self.GetPath())
parent_data = self.section.ReadData(True)
tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
(self.GetPath(), self.offset, self.offset + self.size,
self.size))
data = parent_data[self.offset:self.offset + self.size]
offset = self.offset - self.section._skip_at_start
data = parent_data[offset:offset + self.size]
tout.Info(
'%s: Reading data from offset %#x-%#x (real %#x), size %#x, got %#x' %
(self.GetPath(), self.offset, self.offset + self.size, offset,
self.size, len(data)))
return data
def ReadChildData(self, child, decomp=True):
@@ -648,3 +652,47 @@ class Entry_section(Entry):
"""
for entry in self._entries.values():
entry.CheckMissing(missing_list)
def _CollectEntries(self, entries, entries_by_name, add_entry):
"""Collect all the entries in an section
This builds up a dict of entries in this section and all subsections.
Entries are indexed by path and by name.
Since all paths are unique, entries will not have any conflicts. However
entries_by_name make have conflicts if two entries have the same name
(e.g. with different parent sections). In this case, an entry at a
higher level in the hierarchy will win over a lower-level entry.
Args:
entries: dict to put entries:
key: entry path
value: Entry object
entries_by_name: dict to put entries
key: entry name
value: Entry object
add_entry: Entry to add
"""
entries[add_entry.GetPath()] = add_entry
to_add = add_entry.GetEntries()
if to_add:
for entry in to_add.values():
entries[entry.GetPath()] = entry
for entry in to_add.values():
self._CollectEntries(entries, entries_by_name, entry)
entries_by_name[add_entry.name] = add_entry
def MissingArgs(self, entry, missing):
"""Report a missing argument, if enabled
For entries which require arguments, this reports an error if some are
missing. If missing entries are being ignored (e.g. because we read the
entry from an image rather than creating it), this function does
nothing.
Args:
missing: List of missing properties / entry args, each a string
"""
if not self._ignore_missing:
entry.Raise('Missing required properties/entry args: %s' %
(', '.join(missing)))

View File

@@ -9,7 +9,6 @@
from binman import elf
from binman.entry import Entry
from patman import command
from binman.etype.blob import Entry_blob
from patman import tools

View File

@@ -2,7 +2,7 @@
# Copyright (c) 2016 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Entry-type module for 'u-boot-nodtb.bin'
# Entry-type module for 'u-boot-spl-nodtb.bin'
#
from binman.entry import Entry

View File

@@ -49,7 +49,7 @@ class Entry_vblock(Entry):
EntryArg('kernelkey', str),
EntryArg('preamble-flags', int)])
def ObtainContents(self):
def GetVblock(self):
# Join up the data files to be signed
input_data = b''
for entry_phandle in self.content:
@@ -76,5 +76,16 @@ class Entry_vblock(Entry):
]
#out.Notice("Sign '%s' into %s" % (', '.join(self.value), self.label))
stdout = tools.Run('futility', *args)
self.SetContents(tools.ReadFile(output_fname))
return tools.ReadFile(output_fname)
def ObtainContents(self):
data = self.GetVblock()
if data is False:
return False
self.SetContents(data)
return True
def ProcessContents(self):
# The blob may have changed due to WriteSymbols()
data = self.GetVblock()
return self.ProcessContentsUpdate(data)

View File

@@ -53,8 +53,8 @@ FmapArea = collections.namedtuple('FmapArea', FMAP_AREA_NAMES)
def NameToFmap(name):
if type(name) == bytes and sys.version_info[0] >= 3:
name = name.decode('utf-8') # pragma: no cover (for Python 2)
if type(name) == bytes:
name = name.decode('utf-8')
return name.replace('\0', '').replace('-', '_').upper()
def ConvertName(field_names, fields):

View File

@@ -1638,15 +1638,37 @@ class TestFunctional(unittest.TestCase):
str(e.exception))
def _HandleVblockCommand(self, pipe_list):
"""Fake calls to the futility utility"""
"""Fake calls to the futility utility
The expected pipe is:
[('futility', 'vbutil_firmware', '--vblock',
'vblock.vblock', '--keyblock', 'devkeys/firmware.keyblock',
'--signprivate', 'devkeys/firmware_data_key.vbprivk',
'--version', '1', '--fv', 'input.vblock', '--kernelkey',
'devkeys/kernel_subkey.vbpubk', '--flags', '1')]
This writes to the output file (here, 'vblock.vblock'). If
self._hash_data is False, it writes VBLOCK_DATA, else it writes a hash
of the input data (here, 'input.vblock').
"""
if pipe_list[0][0] == 'futility':
fname = pipe_list[0][3]
with open(fname, 'wb') as fd:
fd.write(VBLOCK_DATA)
if self._hash_data:
infile = pipe_list[0][11]
m = hashlib.sha256()
data = tools.ReadFile(infile)
m.update(data)
fd.write(m.digest())
else:
fd.write(VBLOCK_DATA)
return command.CommandResult()
def testVblock(self):
"""Test for the Chromium OS Verified Boot Block"""
self._hash_data = False
command.test_result = self._HandleVblockCommand
entry_args = {
'keydir': 'devkeys',
@@ -1677,6 +1699,29 @@ class TestFunctional(unittest.TestCase):
self.assertIn("Node '/binman/vblock': Cannot find entry for node "
"'other'", str(e.exception))
def testVblockContent(self):
"""Test that the vblock signs the right data"""
self._hash_data = True
command.test_result = self._HandleVblockCommand
entry_args = {
'keydir': 'devkeys',
}
data = self._DoReadFileDtb(
'189_vblock_content.dts', use_real_dtb=True, update_dtb=True,
entry_args=entry_args)[0]
hashlen = 32 # SHA256 hash is 32 bytes
self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
hashval = data[-hashlen:]
dtb = data[len(U_BOOT_DATA):-hashlen]
expected_data = U_BOOT_DATA + dtb
# The hashval should be a hash of the dtb
m = hashlib.sha256()
m.update(expected_data)
expected_hashval = m.digest()
self.assertEqual(expected_hashval, hashval)
def testTpl(self):
"""Test that an image with TPL and its device tree can be created"""
# ELF file with a '__bss_size' symbol
@@ -4139,6 +4184,67 @@ class TestFunctional(unittest.TestCase):
}
self.assertEqual(expected, props)
def testSymbolsSubsection(self):
"""Test binman can assign symbols from a subsection"""
elf_fname = self.ElfTestFile('u_boot_binman_syms')
syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
self._SetupSplElf('u_boot_binman_syms')
data = self._DoReadFile('187_symbols_sub.dts')
sym_values = struct.pack('<LQLL', 0x00, 0x1c, 0x28, 0x04)
expected = (sym_values + U_BOOT_SPL_DATA[20:] +
tools.GetBytes(0xff, 1) + U_BOOT_DATA + sym_values +
U_BOOT_SPL_DATA[20:])
self.assertEqual(expected, data)
def testReadImageEntryArg(self):
"""Test reading an image that would need an entry arg to generate"""
entry_args = {
'cros-ec-rw-path': 'ecrw.bin',
}
data = self.data = self._DoReadFileDtb(
'188_image_entryarg.dts',use_real_dtb=True, update_dtb=True,
entry_args=entry_args)
image_fname = tools.GetOutputFilename('image.bin')
orig_image = control.images['image']
# This should not generate an error about the missing 'cros-ec-rw-path'
# since we are reading the image from a file. Compare with
# testEntryArgsRequired()
image = Image.FromFile(image_fname)
self.assertEqual(orig_image.GetEntries().keys(),
image.GetEntries().keys())
def testFilesAlign(self):
"""Test alignment with files"""
data = self._DoReadFile('190_files_align.dts')
# The first string is 15 bytes so will align to 16
expect = FILES_DATA[:15] + b'\0' + FILES_DATA[15:]
self.assertEqual(expect, data)
def testReadImageSkip(self):
"""Test reading an image and accessing its FDT map"""
data = self.data = self._DoReadFileRealDtb('191_read_image_skip.dts')
image_fname = tools.GetOutputFilename('image.bin')
orig_image = control.images['image']
image = Image.FromFile(image_fname)
self.assertEqual(orig_image.GetEntries().keys(),
image.GetEntries().keys())
orig_entry = orig_image.GetEntries()['fdtmap']
entry = image.GetEntries()['fdtmap']
self.assertEqual(orig_entry.offset, entry.offset)
self.assertEqual(orig_entry.size, entry.size)
self.assertEqual(16, entry.image_pos)
u_boot = image.GetEntries()['section'].GetEntries()['u-boot']
self.assertEquals(U_BOOT_DATA, u_boot.ReadData())
if __name__ == "__main__":
unittest.main()

View File

@@ -43,8 +43,13 @@ class Image(section.Entry_section):
test: True if this is being called from a test of Images. This this case
there is no device tree defining the structure of the section, so
we create a section manually.
ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
exception). This should be used if the Image is being loaded from
a file rather than generated. In that case we obviously don't need
the entry arguments since the contents already exists.
"""
def __init__(self, name, node, copy_to_orig=True, test=False):
def __init__(self, name, node, copy_to_orig=True, test=False,
ignore_missing=False):
super().__init__(None, 'section', node, test=test)
self.copy_to_orig = copy_to_orig
self.name = 'main-section'
@@ -53,6 +58,7 @@ class Image(section.Entry_section):
self.fdtmap_dtb = None
self.fdtmap_data = None
self.allow_repack = False
self._ignore_missing = ignore_missing
if not test:
self.ReadNode()
@@ -100,7 +106,7 @@ class Image(section.Entry_section):
# Return an Image with the associated nodes
root = dtb.GetRoot()
image = Image('image', root, copy_to_orig=False)
image = Image('image', root, copy_to_orig=False, ignore_missing=True)
image.image_node = fdt_util.GetString(root, 'image-node', 'image')
image.fdtmap_dtb = dtb
@@ -130,12 +136,7 @@ class Image(section.Entry_section):
Returns:
True if the new data size is OK, False if expansion is needed
"""
sizes_ok = True
for entry in self._entries.values():
if not entry.ProcessContents():
sizes_ok = False
tout.Debug("Entry '%s' size change" % self._node.path)
return sizes_ok
return super().ProcessContents()
def WriteSymbols(self):
"""Write symbol values into binary files for access at run time"""
@@ -324,3 +325,48 @@ class Image(section.Entry_section):
_DoLine(lines, _EntryToStrings(entry))
selected_entries.append(entry)
return selected_entries, lines, widths
def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
"""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.
This searches through this image including all of its subsections.
At present the only entry properties supported are:
offset
image_pos - 'base_addr' is added if this is not an end-at-4gb image
size
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
base_addr: Base address of image. This is added to the returned
image_pos in most cases so that the returned position indicates
where the targeted entry/binary has actually been loaded. But
if end-at-4gb is used, this is not done, since the binary is
already assumed to be linked to the ROM position and using
execute-in-place (XIP).
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
"""
entries = OrderedDict()
entries_by_name = {}
self._CollectEntries(entries, entries_by_name, self)
return self.LookupSymbol(sym_name, optional, msg, base_addr,
entries_by_name)

View File

@@ -314,6 +314,16 @@ def AddString(node, prop, value):
for n in GetUpdateNodes(node):
n.AddString(prop, value)
def AddInt(node, prop, value):
"""Add a new string property to affected device trees
Args:
prop_name: Name of property
val: Integer value of property
"""
for n in GetUpdateNodes(node):
n.AddInt(prop, value)
def SetInt(node, prop, value, for_repack=False):
"""Update an integer property in affected device trees with an integer value

View File

@@ -5,7 +5,7 @@
binman {
files {
pattern = "files/*.dat";
compress = "none";
files-compress = "none";
};
};
};

View File

@@ -0,0 +1,22 @@
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
section {
pad-byte = <0xff>;
u-boot-spl {
};
u-boot {
offset = <24>;
};
};
u-boot-spl2 {
type = "u-boot-spl";
};
};
};

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
size = <0xc00>;
u-boot {
};
cros-ec-rw {
};
fdtmap {
};
image-header {
location = "end";
};
};
};

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
u_boot: u-boot {
};
dtb: u-boot-dtb {
};
/*
* Put the vblock after the dtb so that the dtb is updated
* before the vblock reads its data. At present binman does not
* understand dependencies between entries, but simply
* iterates again when it thinks something needs to be
* recalculated.
*/
vblock {
content = <&u_boot &dtb>;
keyblock = "firmware.keyblock";
signprivate = "firmware_data_key.vbprivk";
version = <1>;
kernelkey = "kernel_subkey.vbpubk";
preamble-flags = <1>;
};
};
};

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
binman {
files {
pattern = "files/*.dat";
files-compress = "none";
files-align = <4>;
};
};
};

View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
end-at-4gb;
size = <0x400>;
section {
size = <0x10>;
u-boot {
};
};
fdtmap {
};
image-header {
location = "end";
};
};
};

View File

@@ -460,10 +460,21 @@ class Node:
prop_name: Name of property to add
val: String value of property
"""
if sys.version_info[0] >= 3: # pragma: no cover
val = bytes(val, 'utf-8')
val = bytes(val, 'utf-8')
self.AddData(prop_name, val + b'\0')
def AddInt(self, prop_name, val):
"""Add a new integer property to a node
The device tree is marked dirty so that the value will be written to
the blob on the next sync.
Args:
prop_name: Name of property to add
val: Integer value of property
"""
self.AddData(prop_name, struct.pack('>I', val))
def AddSubnode(self, name):
"""Add a new subnode to the node

View File

@@ -397,6 +397,12 @@ class TestProp(unittest.TestCase):
data = self.fdt.getprop(self.node.Offset(), 'one')
self.assertEqual(1, fdt32_to_cpu(data))
val = 1234
self.node.AddInt('integer', val)
self.dtb.Sync(auto_resize=True)
data = self.fdt.getprop(self.node.Offset(), 'integer')
self.assertEqual(val, fdt32_to_cpu(data))
val = '123' + chr(0) + '456'
self.node.AddString('string', val)
self.dtb.Sync(auto_resize=True)

View File

@@ -476,7 +476,8 @@ def Compress(indata, algo, with_header=True):
fname = GetOutputFilename('%s.comp.tmp' % algo)
WriteFile(fname, indata)
if algo == 'lz4':
data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True)
data = Run('lz4', '--no-frame-crc', '-B4', '-5', '-c', fname,
binary=True)
# cbfstool uses a very old version of lzma
elif algo == 'lzma':
outfname = GetOutputFilename('%s.comp.otmp' % algo)