+cc Sandrine
On Wed, 24 Nov 2021 at 06:09, Simon Glass <s...@chromium.org> wrote: > > Add support for this format which is used by ARM Trusted Firmware to find > firmware binaries to load. > > FIP is like a simpler version of FMAP but uses a UUID instead of a name, > for each entry. > > It supports reading a FIP, writing a FIP and parsing the ATF source code > to get a list of supported UUIDs. > > Signed-off-by: Simon Glass <s...@chromium.org> > --- > > scripts/pylint.base | 8 +- > tools/binman/fip_util.py | 653 ++++++++++++++++++++++++++++++++++ > tools/binman/fip_util_test.py | 405 +++++++++++++++++++++ > tools/binman/main.py | 4 +- > 4 files changed, 1066 insertions(+), 4 deletions(-) > create mode 100755 tools/binman/fip_util.py > create mode 100755 tools/binman/fip_util_test.py > > diff --git a/scripts/pylint.base b/scripts/pylint.base > index 4e82dd4a616..a35dbe34d2d 100644 > --- a/scripts/pylint.base > +++ b/scripts/pylint.base > @@ -9,11 +9,13 @@ binman.elf_test 5.41 > binman.entry 2.39 > binman.entry_test 5.29 > binman.fdt_test 3.23 > +binman.fip_util 9.86 > +binman.fip_util_test 9.75 > binman.fmap_util 6.67 > binman.ftest 7.38 > binman.image 6.39 > binman.image_test 4.48 > -binman.main 4.26 > +binman.main 4.03 > binman.setup 5.00 > binman.state 3.30 > blob -1.94 > @@ -33,7 +35,7 @@ buildman.main 1.43 > buildman.test 6.10 > buildman.toolchain 5.81 > capsule_defs 5.00 > -cbfs -1.52 > +cbfs -1.44 > collection 2.33 > concurrencytest 6.77 > conftest -3.84 > @@ -107,7 +109,7 @@ powerpc_mpc85xx_bootpg_resetvec -10.00 > rkmux 6.76 > rmboard 7.76 > scp -6.00 > -section 4.25 > +section 4.31 > sqfs_common 8.12 > test 8.18 > test_000_version 7.50 > diff --git a/tools/binman/fip_util.py b/tools/binman/fip_util.py > new file mode 100755 > index 00000000000..5f7dbc04d56 > --- /dev/null > +++ b/tools/binman/fip_util.py > @@ -0,0 +1,653 @@ > +#!/usr/bin/env python3 > +# SPDX-License-Identifier: GPL-2.0+ > +# Copyright 2021 Google LLC > +# Written by Simon Glass <s...@chromium.org> > + > +"""Support for ARM's Firmware Image Package (FIP) format > + > +FIP is a format similar to FMAP[1] but with fewer features and an obscure > UUID > +instead of the region name. > + > +It consists of a header and a table of entries, each pointing to a place in > the > +firmware image where something can be found. > + > +[1] > https://chromium.googlesource.com/chromiumos/third_party/flashmap/+/refs/heads/master/lib/fmap.h > + > +If ATF updates, run this program to update the FIT_TYPE_LIST. > + > +ARM Trusted Firmware is available at: > + > +https://github.com/ARM-software/arm-trusted-firmware.git > +""" > + > +from argparse import ArgumentParser > +import collections > +import io > +import os > +import re > +import struct > +import sys > +from uuid import UUID > + > +OUR_FILE = os.path.realpath(__file__) > +OUR_PATH = os.path.dirname(OUR_FILE) > + > +# Bring in the patman and dtoc libraries (but don't override the first path > +# in PYTHONPATH) > +sys.path.insert(2, os.path.join(OUR_PATH, '..')) > + > +# pylint: disable=C0413 > +from patman import command > +from patman import tools > + > +# The TOC header, at the start of the FIP > +HEADER_FORMAT = '<IIQ' > +HEADER_LEN = 0x10 > +HEADER_MAGIC = 0xaA640001 > +HEADER_SERIAL = 0x12345678 > + > +# The entry header (a table of these comes after the TOC header) > +UUID_LEN = 16 > +ENTRY_FORMAT = f'<{UUID_LEN}sQQQ' > +ENTRY_SIZE = 0x28 > + > +HEADER_NAMES = ( > + 'name', > + 'serial', > + 'flags', > +) > + > +ENTRY_NAMES = ( > + 'uuid', > + 'offset', > + 'size', > + 'flags', > +) > + > +# Set to True to enable output from running fiptool for debugging > +VERBOSE = False > + > +# Use a class so we can convert the bytes, making the table more readable > +# pylint: disable=R0903 > +class FipType: > + """A FIP entry type that we understand""" > + def __init__(self, name, desc, uuid_bytes): > + """Create up a new type > + > + Args: > + name (str): Short name for the type > + desc (str): Longer description for the type > + uuid_bytes (bytes): List of 16 bytes for the UUID > + """ > + self.name = name > + self.desc = desc > + self.uuid = bytes(uuid_bytes) > + > +# This is taken from tbbr_config.c in ARM Trusted Firmware > +FIP_TYPE_LIST = [ > + # ToC Entry UUIDs > + FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U', > + [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, > + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), > + FipType('ap-fwu-cfg', 'AP Firmware Updater Configuration BL2U', > + [0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41, > + 0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01]), > + FipType('fwu', 'Firmware Updater NS_BL2U', > + [0x4f, 0x51, 0x1d, 0x11, 0x2b, 0xe5, 0x4e, 0x49, > + 0xb4, 0xc5, 0x83, 0xc2, 0xf7, 0x15, 0x84, 0x0a]), > + FipType('fwu-cert', 'Non-Trusted Firmware Updater certificate', > + [0x71, 0x40, 0x8a, 0xb2, 0x18, 0xd6, 0x87, 0x4c, > + 0x8b, 0x2e, 0xc6, 0xdc, 0xcd, 0x50, 0xf0, 0x96]), > + FipType('tb-fw', 'Trusted Boot Firmware BL2', > + [0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d, > + 0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a]), > + FipType('scp-fw', 'SCP Firmware SCP_BL2', > + [0x97, 0x66, 0xfd, 0x3d, 0x89, 0xbe, 0xe8, 0x49, > + 0xae, 0x5d, 0x78, 0xa1, 0x40, 0x60, 0x82, 0x13]), > + FipType('soc-fw', 'EL3 Runtime Firmware BL31', > + [0x47, 0xd4, 0x08, 0x6d, 0x4c, 0xfe, 0x98, 0x46, > + 0x9b, 0x95, 0x29, 0x50, 0xcb, 0xbd, 0x5a, 0x00]), > + FipType('tos-fw', 'Secure Payload BL32 (Trusted OS)', > + [0x05, 0xd0, 0xe1, 0x89, 0x53, 0xdc, 0x13, 0x47, > + 0x8d, 0x2b, 0x50, 0x0a, 0x4b, 0x7a, 0x3e, 0x38]), > + FipType('tos-fw-extra1', 'Secure Payload BL32 Extra1 (Trusted OS > Extra1)', > + [0x0b, 0x70, 0xc2, 0x9b, 0x2a, 0x5a, 0x78, 0x40, > + 0x9f, 0x65, 0x0a, 0x56, 0x82, 0x73, 0x82, 0x88]), > + FipType('tos-fw-extra2', 'Secure Payload BL32 Extra2 (Trusted OS > Extra2)', > + [0x8e, 0xa8, 0x7b, 0xb1, 0xcf, 0xa2, 0x3f, 0x4d, > + 0x85, 0xfd, 0xe7, 0xbb, 0xa5, 0x02, 0x20, 0xd9]), > + FipType('nt-fw', 'Non-Trusted Firmware BL33', > + [0xd6, 0xd0, 0xee, 0xa7, 0xfc, 0xea, 0xd5, 0x4b, > + 0x97, 0x82, 0x99, 0x34, 0xf2, 0x34, 0xb6, 0xe4]), > + FipType('rmm-fw', 'Realm Monitor Management Firmware', > + [0x6c, 0x07, 0x62, 0xa6, 0x12, 0xf2, 0x4b, 0x56, > + 0x92, 0xcb, 0xba, 0x8f, 0x63, 0x36, 0x06, 0xd9]), > + # Key certificates > + FipType('rot-cert', 'Root Of Trust key certificate', > + [0x86, 0x2d, 0x1d, 0x72, 0xf8, 0x60, 0xe4, 0x11, > + 0x92, 0x0b, 0x8b, 0xe7, 0x62, 0x16, 0x0f, 0x24]), > + FipType('trusted-key-cert', 'Trusted key certificate', > + [0x82, 0x7e, 0xe8, 0x90, 0xf8, 0x60, 0xe4, 0x11, > + 0xa1, 0xb4, 0x77, 0x7a, 0x21, 0xb4, 0xf9, 0x4c]), > + FipType('scp-fw-key-cert', 'SCP Firmware key certificate', > + [0x02, 0x42, 0x21, 0xa1, 0xf8, 0x60, 0xe4, 0x11, > + 0x8d, 0x9b, 0xf3, 0x3c, 0x0e, 0x15, 0xa0, 0x14]), > + FipType('soc-fw-key-cert', 'SoC Firmware key certificate', > + [0x8a, 0xb8, 0xbe, 0xcc, 0xf9, 0x60, 0xe4, 0x11, > + 0x9a, 0xd0, 0xeb, 0x48, 0x22, 0xd8, 0xdc, 0xf8]), > + FipType('tos-fw-key-cert', 'Trusted OS Firmware key certificate', > + [0x94, 0x77, 0xd6, 0x03, 0xfb, 0x60, 0xe4, 0x11, > + 0x85, 0xdd, 0xb7, 0x10, 0x5b, 0x8c, 0xee, 0x04]), > + FipType('nt-fw-key-cert', 'Non-Trusted Firmware key certificate', > + [0x8a, 0xd5, 0x83, 0x2a, 0xfb, 0x60, 0xe4, 0x11, > + 0x8a, 0xaf, 0xdf, 0x30, 0xbb, 0xc4, 0x98, 0x59]), > + # Content certificates > + FipType('tb-fw-cert', 'Trusted Boot Firmware BL2 certificate', > + [0xd6, 0xe2, 0x69, 0xea, 0x5d, 0x63, 0xe4, 0x11, > + 0x8d, 0x8c, 0x9f, 0xba, 0xbe, 0x99, 0x56, 0xa5]), > + FipType('scp-fw-cert', 'SCP Firmware content certificate', > + [0x44, 0xbe, 0x6f, 0x04, 0x5e, 0x63, 0xe4, 0x11, > + 0xb2, 0x8b, 0x73, 0xd8, 0xea, 0xae, 0x96, 0x56]), > + FipType('soc-fw-cert', 'SoC Firmware content certificate', > + [0xe2, 0xb2, 0x0c, 0x20, 0x5e, 0x63, 0xe4, 0x11, > + 0x9c, 0xe8, 0xab, 0xcc, 0xf9, 0x2b, 0xb6, 0x66]), > + FipType('tos-fw-cert', 'Trusted OS Firmware content certificate', > + [0xa4, 0x9f, 0x44, 0x11, 0x5e, 0x63, 0xe4, 0x11, > + 0x87, 0x28, 0x3f, 0x05, 0x72, 0x2a, 0xf3, 0x3d]), > + FipType('nt-fw-cert', 'Non-Trusted Firmware content certificate', > + [0x8e, 0xc4, 0xc1, 0xf3, 0x5d, 0x63, 0xe4, 0x11, > + 0xa7, 0xa9, 0x87, 0xee, 0x40, 0xb2, 0x3f, 0xa7]), > + FipType('sip-sp-cert', 'SiP owned Secure Partition content certificate', > + [0x77, 0x6d, 0xfd, 0x44, 0x86, 0x97, 0x4c, 0x3b, > + 0x91, 0xeb, 0xc1, 0x3e, 0x02, 0x5a, 0x2a, 0x6f]), > + FipType('plat-sp-cert', 'Platform owned Secure Partition content > certificate', > + [0xdd, 0xcb, 0xbf, 0x4a, 0xca, 0xd6, 0x11, 0xea, > + 0x87, 0xd0, 0x02, 0x42, 0xac, 0x13, 0x00, 0x03]), > + # Dynamic configs > + FipType('hw-config', 'HW_CONFIG', > + [0x08, 0xb8, 0xf1, 0xd9, 0xc9, 0xcf, 0x93, 0x49, > + 0xa9, 0x62, 0x6f, 0xbc, 0x6b, 0x72, 0x65, 0xcc]), > + FipType('tb-fw-config', 'TB_FW_CONFIG', > + [0x6c, 0x04, 0x58, 0xff, 0xaf, 0x6b, 0x7d, 0x4f, > + 0x82, 0xed, 0xaa, 0x27, 0xbc, 0x69, 0xbf, 0xd2]), > + FipType('soc-fw-config', 'SOC_FW_CONFIG', > + [0x99, 0x79, 0x81, 0x4b, 0x03, 0x76, 0xfb, 0x46, > + 0x8c, 0x8e, 0x8d, 0x26, 0x7f, 0x78, 0x59, 0xe0]), > + FipType('tos-fw-config', 'TOS_FW_CONFIG', > + [0x26, 0x25, 0x7c, 0x1a, 0xdb, 0xc6, 0x7f, 0x47, > + 0x8d, 0x96, 0xc4, 0xc4, 0xb0, 0x24, 0x80, 0x21]), > + FipType('nt-fw-config', 'NT_FW_CONFIG', > + [0x28, 0xda, 0x98, 0x15, 0x93, 0xe8, 0x7e, 0x44, > + 0xac, 0x66, 0x1a, 0xaf, 0x80, 0x15, 0x50, 0xf9]), > + FipType('fw-config', 'FW_CONFIG', > + [0x58, 0x07, 0xe1, 0x6a, 0x84, 0x59, 0x47, 0xbe, > + 0x8e, 0xd5, 0x64, 0x8e, 0x8d, 0xdd, 0xab, 0x0e]), > + ] # end > + > +FIP_TYPES = {ftype.name: ftype for ftype in FIP_TYPE_LIST} > + > + > +def get_type_uuid(fip_type_or_uuid): > + """get_type_uuid() - Convert a type or uuid into both > + > + This always returns a UUID, but may not return a type since it does not > do > + the reverse lookup. > + > + Args: > + fip_type_or_uuid (str or bytes): Either a string containing the name > of > + an entry (e.g. 'soc-fw') or a bytes(16) containing the UUID > + > + Returns: > + tuple: > + str: fip type (None if not known) > + bytes(16): uuid > + > + Raises: > + ValueError: An unknown type was requested > + """ > + if isinstance(fip_type_or_uuid, str): > + fip_type = fip_type_or_uuid > + lookup = FIP_TYPES.get(fip_type) > + if not lookup: > + raise ValueError(f"Unknown FIP entry type '{fip_type}'") > + uuid = lookup.uuid > + else: > + fip_type = None > + uuid = fip_type_or_uuid > + return fip_type, uuid > + > + > +# pylint: disable=R0903 > +class FipHeader: > + """Class to represent a FIP header""" > + def __init__(self, name, serial, flags): > + """Set up a new header object > + > + Args: > + name (str): Name, i.e. HEADER_MAGIC > + serial (str): Serial value, i.e. HEADER_SERIAL > + flags (int64): Flags value > + """ > + self.name = name > + self.serial = serial > + self.flags = flags > + > + > +# pylint: disable=R0903 > +class FipEntry: > + """Class to represent a single FIP entry > + > + This is used to hold the information about an entry, including its > contents. > + Use the get_data() method to obtain the raw output for writing to the FIP > + file. > + """ > + def __init__(self, uuid, offset, size, flags): > + self.uuid = uuid > + self.offset = offset > + self.size = size > + self.flags = flags > + self.fip_type = None > + self.data = None > + self.valid = uuid != tools.GetBytes(0, UUID_LEN) > + if self.valid: > + # Look up the friendly name > + matches = {val for (key, val) in FIP_TYPES.items() > + if val.uuid == uuid} > + if len(matches) == 1: > + self.fip_type = matches.pop().name > + > + @classmethod > + def from_type(cls, fip_type_or_uuid, data, flags): > + """Create a FipEntry from a type name > + > + Args: > + cls (class): This class > + fip_type_or_uuid (str or bytes): Name of the type to create, or > + bytes(16) uuid > + data (bytes): Contents of entry > + flags (int64): Flags value > + > + Returns: > + FipEntry: Created 241 > + """ > + fip_type, uuid = get_type_uuid(fip_type_or_uuid) > + fent = FipEntry(uuid, None, len(data), flags) > + fent.fip_type = fip_type > + fent.data = data > + return fent > + > + > +def decode_fip(data): > + """Decode a FIP into a header and list of FIP entries > + > + Args: > + data (bytes): Data block containing the FMAP > + > + Returns: > + Tuple: > + header: FipHeader object > + List of FipArea objects > + """ > + fields = list(struct.unpack(HEADER_FORMAT, data[:HEADER_LEN])) > + header = FipHeader(*fields) > + fents = [] > + pos = HEADER_LEN > + while True: > + fields = list(struct.unpack(ENTRY_FORMAT, data[pos:pos + > ENTRY_SIZE])) > + fent = FipEntry(*fields) > + if not fent.valid: > + break > + fent.data = data[fent.offset:fent.offset + fent.size] > + fents.append(fent) > + pos += ENTRY_SIZE > + return header, fents > + > + > +class FipWriter: > + """Class to handle writing a ARM Trusted Firmware's Firmware Image > Package > + > + Usage is something like: > + > + fip = FipWriter(size) > + fip.add_entry('scp-fwu-cfg', tools.ReadFile('something.bin')) > + ... > + data = cbw.get_data() > + > + Attributes: > + """ > + def __init__(self, flags, align): > + self._fip_entries = [] > + self._flags = flags > + self._align = align > + > + def add_entry(self, fip_type, data, flags): > + """Add a new entry to the FIP > + > + Args: > + fip_type (str): Type to add, e.g. 'tos-fw-config' > + data (bytes): Contents of entry > + flags (int64): Entry flags > + > + Returns: > + FipEntry: entry that was added > + """ > + fent = FipEntry.from_type(fip_type, data, flags) > + self._fip_entries.append(fent) > + return fent > + > + def get_data(self): > + """Obtain the full contents of the FIP > + > + Thhis builds the FIP with headers and all required FIP entries. > + > + Returns: > + bytes: data resulting from building the FIP > + """ > + buf = io.BytesIO() > + hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_SERIAL, > + self._flags) > + buf.write(hdr) > + > + # Calculate the position fo the first entry > + offset = len(hdr) > + offset += len(self._fip_entries) * ENTRY_SIZE > + offset += ENTRY_SIZE # terminating entry > + > + for fent in self._fip_entries: > + offset = tools.Align(offset, self._align) > + fent.offset = offset > + offset += fent.size > + > + # Write out the TOC > + for fent in self._fip_entries: > + hdr = struct.pack(ENTRY_FORMAT, fent.uuid, fent.offset, > fent.size, > + fent.flags) > + buf.write(hdr) > + > + # Write out the entries > + for fent in self._fip_entries: > + buf.seek(fent.offset) > + buf.write(fent.data) > + > + return buf.getvalue() > + > + > +class FipReader(): > + """Class to handle reading a Firmware Image Package (FIP) > + > + Usage is something like: > + fip = fip_util.FipReader(data) > + fent = fip.get_entry('fwu') > + self.WriteFile('ufwu.bin', fent.data) > + blob = fip.get_entry( > + bytes([0xe3, 0xb7, 0x8d, 0x9e, 0x4a, 0x64, 0x11, 0xec, > + 0xb4, 0x5c, 0xfb, 0xa2, 0xb9, 0xb4, 0x97, 0x88])) > + self.WriteFile('blob.bin', blob.data) > + """ > + def __init__(self, data, read=True): > + """Set up a new FitReader > + > + Args: > + data (bytes): data to read > + read (bool): True to read the data now > + """ > + self.fents = collections.OrderedDict() > + self.data = data > + if read: > + self.read() > + > + def read(self): > + """Read all the files in the FIP and add them to self.files""" > + self.header, self.fents = decode_fip(self.data) > + > + def get_entry(self, fip_type_or_uuid): > + """get_entry() - Find an entry by type or UUID > + > + Args: > + fip_type_or_uuid (str or bytes): Name of the type to create, or > + bytes(16) uuid > + > + Returns: > + FipEntry: if found > + > + Raises: > + ValueError: entry type not found > + """ > + fip_type, uuid = get_type_uuid(fip_type_or_uuid) > + for fent in self.fents: > + if fent.uuid == uuid: > + return fent > + label = fip_type > + if not label: > + label = UUID(bytes=uuid) > + raise ValueError(f"Cannot find FIP entry '{label}'") > + > + > +def parse_macros(srcdir): > + """parse_macros: Parse the firmware_image_package.h file > + > + Args: > + srcdir (str): 'arm-trusted-firmware' source directory > + > + Returns: > + dict: > + key: UUID macro name, e.g. 'UUID_TRUSTED_FWU_CERT' > + value: list: > + file comment, e.g. 'ToC Entry UUIDs' > + macro name, e.g. 'UUID_TRUSTED_FWU_CERT' > + uuid as bytes(16) > + > + Raises: > + ValueError: a line cannot be parsed > + """ > + re_uuid = re.compile('0x[0-9a-fA-F]{2}') > + re_comment = re.compile(r'^/\* (.*) \*/$') > + fname = os.path.join(srcdir, > 'include/tools_share/firmware_image_package.h') > + data = tools.ReadFile(fname, binary=False) > + macros = collections.OrderedDict() > + comment = None > + for linenum, line in enumerate(data.splitlines()): > + if line.startswith('/*'): > + mat = re_comment.match(line) > + if mat: > + comment = mat.group(1) > + else: > + # Example: #define UUID_TOS_FW_CONFIG \ > + if 'UUID' in line: > + macro = line.split()[1] > + elif '{{' in line: > + mat = re_uuid.findall(line) > + if not mat or len(mat) != 16: > + raise ValueError( > + f'{fname}: Cannot parse UUID line {linenum + 1}: Got > matches: {mat}') > + > + uuid = bytes([int(val, 16) for val in mat]) > + macros[macro] = comment, macro, uuid > + if not macros: > + raise ValueError(f'{fname}: Cannot parse file') > + return macros > + > + > +def parse_names(srcdir): > + """parse_names: Parse the tbbr_config.c file > + > + Args: > + srcdir (str): 'arm-trusted-firmware' source directory > + > + Returns: > + tuple: dict of entries: > + key: UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33' > + tuple: entry information > + Description of entry, e.g. 'Non-Trusted Firmware BL33' > + UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33' > + Name of entry, e.g. 'nt-fw' > + > + Raises: > + ValueError: the file cannot be parsed > + """ > + # Extract the .name, .uuid and .cmdline_name values > + re_data = re.compile(r'\.name = "([^"]*)",\s*\.uuid = > (UUID_\w*),\s*\.cmdline_name = "([^"]+)"', > + re.S) > + fname = os.path.join(srcdir, 'tools/fiptool/tbbr_config.c') > + data = tools.ReadFile(fname, binary=False) > + > + # Example entry: > + # { > + # .name = "Secure Payload BL32 Extra2 (Trusted OS Extra2)", > + # .uuid = UUID_SECURE_PAYLOAD_BL32_EXTRA2, > + # .cmdline_name = "tos-fw-extra2" > + # }, > + mat = re_data.findall(data) > + if not mat: > + raise ValueError(f'{fname}: Cannot parse file') > + names = {uuid: (desc, uuid, name) for desc, uuid, name in mat} > + return names > + > + > +def create_code_output(macros, names): > + """create_code_output() - Create the new version of this Python file > + > + Args: > + macros (dict): > + key (str): UUID macro name, e.g. 'UUID_TRUSTED_FWU_CERT' > + value: list: > + file comment, e.g. 'ToC Entry UUIDs' > + macro name, e.g. 'UUID_TRUSTED_FWU_CERT' > + uuid as bytes(16) > + > + names (dict): list of entries, each > + tuple: entry information > + Description of entry, e.g. 'Non-Trusted Firmware BL33' > + UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33' > + Name of entry, e.g. 'nt-fw' > + > + Returns: > + str: Table of FipType() entries > + """ > + def _to_hex_list(data): > + """Convert bytes into C code > + > + Args: > + bytes to convert > + > + Returns: > + str: in the format '0x12, 0x34, 0x56...' > + """ > + # Use 0x instead of %# since the latter ignores the 0 modifier in > + # Python 3.8.10 > + return ', '.join(['0x%02x' % byte for byte in data]) > + > + out = '' > + last_comment = None > + for comment, macro, uuid in macros.values(): > + name_entry = names.get(macro) > + if not name_entry: > + print(f"Warning: UUID '{macro}' is not mentioned in > tbbr_config.c file") > + continue > + desc, _, name = name_entry > + if last_comment != comment: > + out += f' # {comment}\n' > + last_comment = comment > + out += """ FipType('%s', '%s', > + [%s, > + %s]), > +""" % (name, desc, _to_hex_list(uuid[:8]), _to_hex_list(uuid[8:])) > + return out > + > + > +def parse_atf_source(srcdir, dstfile, oldfile): > + """parse_atf_source(): Parse the ATF source tree and update this file > + > + Args: > + srcdir (str): Path to 'arm-trusted-firmware' directory. Get this > from: > + https://github.com/ARM-software/arm-trusted-firmware.git > + dstfile (str): File to write new code to, if an update is needed > + oldfile (str): Python source file to compare against > + > + Raises: > + ValueError: srcdir readme.rst is missing or the first line does not > + match what is expected > + """ > + # We expect a readme file > + readme_fname = os.path.join(srcdir, 'readme.rst') > + if not os.path.exists(readme_fname): > + raise ValueError( > + f"Expected file '{readme_fname}' - try using -s to specify the " > + 'arm-trusted-firmware directory') > + readme = tools.ReadFile(readme_fname, binary=False) > + first_line = 'Trusted Firmware-A' > + if readme.splitlines()[0] != first_line: > + raise ValueError(f"'{readme_fname}' does not start with > '{first_line}'") > + macros = parse_macros(srcdir) > + names = parse_names(srcdir) > + output = create_code_output(macros, names) > + orig = tools.ReadFile(oldfile, binary=False) > + re_fip_list = re.compile(r'(.*FIP_TYPE_LIST = \[).*?( ] # end.*)', > re.S) > + mat = re_fip_list.match(orig) > + new_code = mat.group(1) + '\n' + output + mat.group(2) if mat else output > + if new_code == orig: > + print(f"Existing code in '{oldfile}' is up-to-date") > + else: > + tools.WriteFile(dstfile, new_code, binary=False) > + print(f'Needs update, try:\n\tmeld {dstfile} {oldfile}') > + > + > +def main(argv, oldfile): > + """Main program for this tool > + > + Args: > + argv (list): List of str command-line arguments > + oldfile (str): Python source file to compare against > + > + Returns: > + int: 0 (exit code) > + """ > + parser = ArgumentParser(epilog='''Creates an updated version of this > code, > +with a table of FIP-entry types parsed from the arm-trusted-firmware source > +directory''') > + parser.add_argument( > + '-D', '--debug', action='store_true', > + help='Enabling debugging (provides a full traceback on error)') > + parser.add_argument( > + '-o', '--outfile', type=str, default='fip_util.py.out', > + help='Output file to write new fip_util.py file to') > + parser.add_argument( > + '-s', '--src', type=str, default='.', > + help='Directory containing the arm-trusted-firmware source') > + args = parser.parse_args(argv) > + > + if not args.debug: > + sys.tracebacklimit = 0 > + > + parse_atf_source(args.src, args.outfile, oldfile) > + return 0 > + > + > +def fiptool(fname, *fip_args): > + """Run fiptool with provided arguments > + > + If the tool fails then this function raises an exception and prints out > the > + output and stderr. > + > + Args: > + fname (str): Filename of FIP > + *fip_args: List of arguments to pass to fiptool > + > + Returns: > + CommandResult: object containing the results > + > + Raises: > + ValueError: the tool failed to run > + """ > + args = ['fiptool', fname] + list(fip_args) > + result = command.RunPipe([args], capture=not VERBOSE, > + capture_stderr=not VERBOSE, > raise_on_error=False) > + if result.return_code: > + print(result.stderr, file=sys.stderr) > + raise ValueError("Failed to run (error %d): '%s'" % > + (result.return_code, ' '.join(args))) > + return result > + > + > +if __name__ == "__main__": > + sys.exit(main(sys.argv[1:], OUR_FILE)) # pragma: no cover > diff --git a/tools/binman/fip_util_test.py b/tools/binman/fip_util_test.py > new file mode 100755 > index 00000000000..4839b298762 > --- /dev/null > +++ b/tools/binman/fip_util_test.py > @@ -0,0 +1,405 @@ > +#!/usr/bin/env python3 > +# SPDX-License-Identifier: GPL-2.0+ > +# Copyright 2021 Google LLC > +# Written by Simon Glass <s...@chromium.org> > + > +"""Tests for fip_util > + > +This tests a few features of fip_util which are not covered by binman's > ftest.py > +""" > + > +import os > +import shutil > +import sys > +import tempfile > +import unittest > + > +# Bring in the patman and dtoc libraries (but don't override the first path > +# in PYTHONPATH) > +OUR_PATH = os.path.dirname(os.path.realpath(__file__)) > +sys.path.insert(2, os.path.join(OUR_PATH, '..')) > + > +# pylint: disable=C0413 > +from patman import test_util > +from patman import tools > +import fip_util > + > +HAVE_FIPTOOL = True > +try: > + tools.Run('which', 'fiptool') > +except ValueError: > + HAVE_FIPTOOL = False > + > +# pylint: disable=R0902,R0904 > +class TestFip(unittest.TestCase): > + """Test of fip_util classes""" > + #pylint: disable=W0212 > + def setUp(self): > + # Create a temporary directory for test files > + self._indir = tempfile.mkdtemp(prefix='fip_util.') > + tools.SetInputDirs([self._indir]) > + > + # Set up a temporary output directory, used by the tools library when > + # compressing files > + tools.PrepareOutputDir(None) > + > + self.src_file = os.path.join(self._indir, 'orig.py') > + self.outname = tools.GetOutputFilename('out.py') > + self.args = ['-D', '-s', self._indir, '-o', self.outname] > + self.readme = os.path.join(self._indir, 'readme.rst') > + self.macro_dir = os.path.join(self._indir, 'include/tools_share') > + self.macro_fname = os.path.join(self.macro_dir, > + 'firmware_image_package.h') > + self.name_dir = os.path.join(self._indir, 'tools/fiptool') > + self.name_fname = os.path.join(self.name_dir, 'tbbr_config.c') > + > + macro_contents = ''' > + > +/* ToC Entry UUIDs */ > +#define UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U \\ > + {{0x65, 0x92, 0x27, 0x03}, {0x2f, 0x74}, {0xe6, 0x44}, 0x8d, 0xff, > {0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10} } > +#define UUID_TRUSTED_UPDATE_FIRMWARE_BL2U \\ > + {{0x60, 0xb3, 0xeb, 0x37}, {0xc1, 0xe5}, {0xea, 0x41}, 0x9d, 0xf3, > {0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01} } > + > +''' > + > + name_contents = ''' > + > +toc_entry_t toc_entries[] = { > + { > + .name = "SCP Firmware Updater Configuration FWU SCP_BL2U", > + .uuid = UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U, > + .cmdline_name = "scp-fwu-cfg" > + }, > + { > + .name = "AP Firmware Updater Configuration BL2U", > + .uuid = UUID_TRUSTED_UPDATE_FIRMWARE_BL2U, > + .cmdline_name = "ap-fwu-cfg" > + }, > +''' > + > + def setup_readme(self): > + """Set up the readme.txt file""" > + tools.WriteFile(self.readme, 'Trusted > Firmware-A\n==================', > + binary=False) > + > + def setup_macro(self, data=macro_contents): > + """Set up the tbbr_config.c file""" > + os.makedirs(self.macro_dir) > + tools.WriteFile(self.macro_fname, data, binary=False) > + > + def setup_name(self, data=name_contents): > + """Set up the firmware_image_package.h file""" > + os.makedirs(self.name_dir) > + tools.WriteFile(self.name_fname, data, binary=False) > + > + def tearDown(self): > + """Remove the temporary input directory and its contents""" > + if self._indir: > + shutil.rmtree(self._indir) > + self._indir = None > + tools.FinaliseOutputDir() > + > + def test_no_readme(self): > + """Test handling of a missing readme.rst""" > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('Expected file', str(err.exception)) > + > + def test_invalid_readme(self): > + """Test that an invalid readme.rst is detected""" > + tools.WriteFile(self.readme, 'blah', binary=False) > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('does not start with', str(err.exception)) > + > + def test_no_fip_h(self): > + """Check handling of missing firmware_image_package.h""" > + self.setup_readme() > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('No such file or directory', str(err.exception)) > + > + def test_invalid_fip_h(self): > + """Check failure to parse firmware_image_package.h""" > + self.setup_readme() > + self.setup_macro('blah') > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('Cannot parse file', str(err.exception)) > + > + def test_parse_fip_h(self): > + """Check parsing of firmware_image_package.h""" > + self.setup_readme() > + # Check parsing the header file > + self.setup_macro() > + macros = fip_util.parse_macros(self._indir) > + expected_macros = { > + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U': > + ('ToC Entry UUIDs', 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U', > + bytes([0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, > + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10])), > + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U': > + ('ToC Entry UUIDs', 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U', > + bytes([0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41, > + 0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01])), > + } > + self.assertEqual(expected_macros, macros) > + > + def test_missing_tbbr_c(self): > + """Check handlinh of missing tbbr_config.c""" > + self.setup_readme() > + self.setup_macro() > + > + # Still need the .c file > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('tbbr_config.c', str(err.exception)) > + > + def test_invalid_tbbr_c(self): > + """Check failure to parse tbbr_config.c""" > + self.setup_readme() > + self.setup_macro() > + # Check invalid format for C file > + self.setup_name('blah') > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('Cannot parse file', str(err.exception)) > + > + def test_inconsistent_tbbr_c(self): > + """Check tbbr_config.c in a format we don't expect""" > + self.setup_readme() > + # This is missing a hex value > + self.setup_macro(''' > + > +/* ToC Entry UUIDs */ > +#define UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U \\ > + {{0x65, 0x92, 0x27,}, {0x2f, 0x74}, {0xe6, 0x44}, 0x8d, 0xff, {0x57, > 0x9a, 0xc1, 0xff, 0x06, 0x10} } > +#define UUID_TRUSTED_UPDATE_FIRMWARE_BL2U \\ > + {{0x60, 0xb3, 0xeb, 0x37}, {0xc1, 0xe5}, {0xea, 0x41}, 0x9d, 0xf3, > {0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01} } > + > +''') > + # Check invalid format for C file > + self.setup_name('blah') > + with self.assertRaises(Exception) as err: > + fip_util.main(self.args, self.src_file) > + self.assertIn('Cannot parse UUID line 5', str(err.exception)) > + > + def test_parse_tbbr_c(self): > + """Check parsing tbbr_config.c""" > + self.setup_readme() > + self.setup_macro() > + self.setup_name() > + > + names = fip_util.parse_names(self._indir) > + > + expected_names = { > + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U': ( > + 'SCP Firmware Updater Configuration FWU SCP_BL2U', > + 'UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U', > + 'scp-fwu-cfg'), > + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U': ( > + 'AP Firmware Updater Configuration BL2U', > + 'UUID_TRUSTED_UPDATE_FIRMWARE_BL2U', > + 'ap-fwu-cfg'), > + } > + self.assertEqual(expected_names, names) > + > + def test_uuid_not_in_tbbr_config_c(self): > + """Check handling a UUID in the header file that's not in the .c > file""" > + self.setup_readme() > + self.setup_macro(self.macro_contents + ''' > +#define UUID_TRUSTED_OS_FW_KEY_CERT \\ > + {{0x94, 0x77, 0xd6, 0x03}, {0xfb, 0x60}, {0xe4, 0x11}, 0x85, 0xdd, > {0xb7, 0x10, 0x5b, 0x8c, 0xee, 0x04} } > + > +''') > + self.setup_name() > + > + macros = fip_util.parse_macros(self._indir) > + names = fip_util.parse_names(self._indir) > + with test_util.capture_sys_output() as (stdout, _): > + fip_util.create_code_output(macros, names) > + self.assertIn( > + "UUID 'UUID_TRUSTED_OS_FW_KEY_CERT' is not mentioned in > tbbr_config.c file", > + stdout.getvalue()) > + > + def test_changes(self): > + """Check handling of a source file that does/doesn't need changes""" > + self.setup_readme() > + self.setup_macro() > + self.setup_name() > + > + # Check generating the file when changes are needed > + tools.WriteFile(self.src_file, ''' > + > +# This is taken from tbbr_config.c in ARM Trusted Firmware > +FIP_TYPE_LIST = [ > + # ToC Entry UUIDs > + FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U', > + [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, > + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), > + ] # end > +blah de blah > + ''', binary=False) > + with test_util.capture_sys_output() as (stdout, _): > + fip_util.main(self.args, self.src_file) > + self.assertIn('Needs update', stdout.getvalue()) > + > + # Check generating the file when no changes are needed > + tools.WriteFile(self.src_file, ''' > +# This is taken from tbbr_config.c in ARM Trusted Firmware > +FIP_TYPE_LIST = [ > + # ToC Entry UUIDs > + FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U', > + [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44, > + 0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), > + FipType('ap-fwu-cfg', 'AP Firmware Updater Configuration BL2U', > + [0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41, > + 0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01]), > + ] # end > +blah blah''', binary=False) > + with test_util.capture_sys_output() as (stdout, _): > + fip_util.main(self.args, self.src_file) > + self.assertIn('is up-to-date', stdout.getvalue()) > + > + def test_no_debug(self): > + """Test running without the -D flag""" > + self.setup_readme() > + self.setup_macro() > + self.setup_name() > + > + args = self.args.copy() > + args.remove('-D') > + tools.WriteFile(self.src_file, '', binary=False) > + with test_util.capture_sys_output(): > + fip_util.main(args, self.src_file) > + > + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') > + def test_fiptool_list(self): > + """Create a FIP and check that fiptool can read it""" > + fwu = b'my data' > + tb_fw = b'some more data' > + fip = fip_util.FipWriter(0x123, 0x10) > + fip.add_entry('fwu', fwu, 0x456) > + fip.add_entry('tb-fw', tb_fw, 0) > + fip.add_entry(bytes(range(16)), tb_fw, 0) > + data = fip.get_data() > + fname = tools.GetOutputFilename('data.fip') > + tools.WriteFile(fname, data) > + result = fip_util.fiptool('info', fname) > + self.assertEqual( > + '''Firmware Updater NS_BL2U: offset=0xB0, size=0x7, > cmdline="--fwu" > +Trusted Boot Firmware BL2: offset=0xC0, size=0xE, cmdline="--tb-fw" > +00010203-0405-0607-0809-0A0B0C0D0E0F: offset=0xD0, size=0xE, cmdline="--blob" > +''', > + result.stdout) > + > + fwu_data = b'my data' > + tb_fw_data = b'some more data' > + other_fw_data = b'even more' > + > + def create_fiptool_image(self): > + """Create an image with fiptool which we can use for testing > + > + Returns: > + FipReader: reader for the image > + """ > + fwu = os.path.join(self._indir, 'fwu') > + tools.WriteFile(fwu, self.fwu_data) > + > + tb_fw = os.path.join(self._indir, 'tb_fw') > + tools.WriteFile(tb_fw, self.tb_fw_data) > + > + other_fw = os.path.join(self._indir, 'other_fw') > + tools.WriteFile(other_fw, self.other_fw_data) > + > + fname = tools.GetOutputFilename('data.fip') > + uuid = 'e3b78d9e-4a64-11ec-b45c-fba2b9b49788' > + fip_util.fiptool('create', '--align', '8', '--plat-toc-flags', > '0x123', > + '--fwu', fwu, > + '--tb-fw', tb_fw, > + '--blob', f'uuid={uuid},file={other_fw}', > + fname) > + > + return fip_util.FipReader(tools.ReadFile(fname)) > + > + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') > + def test_fiptool_create(self): > + """Create a FIP with fiptool and check that fip_util can read it""" > + reader = self.create_fiptool_image() > + > + header = reader.header > + fents = reader.fents > + > + self.assertEqual(0x123 << 32, header.flags) > + self.assertEqual(fip_util.HEADER_MAGIC, header.name) > + self.assertEqual(fip_util.HEADER_SERIAL, header.serial) > + > + self.assertEqual(3, len(fents)) > + fent = fents[0] > + self.assertEqual( > + bytes([0x4f, 0x51, 0x1d, 0x11, 0x2b, 0xe5, 0x4e, 0x49, > + 0xb4, 0xc5, 0x83, 0xc2, 0xf7, 0x15, 0x84, 0x0a]), > fent.uuid) > + self.assertEqual(0xb0, fent.offset) > + self.assertEqual(len(self.fwu_data), fent.size) > + self.assertEqual(0, fent.flags) > + self.assertEqual(self.fwu_data, fent.data) > + > + fent = fents[1] > + self.assertEqual( > + bytes([0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d, > + 0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a]), fent.uuid) > + self.assertEqual(0xb8, fent.offset) > + self.assertEqual(len(self.tb_fw_data), fent.size) > + self.assertEqual(0, fent.flags) > + self.assertEqual(self.tb_fw_data, fent.data) > + > + fent = fents[2] > + self.assertEqual( > + bytes([0xe3, 0xb7, 0x8d, 0x9e, 0x4a, 0x64, 0x11, 0xec, > + 0xb4, 0x5c, 0xfb, 0xa2, 0xb9, 0xb4, 0x97, 0x88]), > fent.uuid) > + self.assertEqual(0xc8, fent.offset) > + self.assertEqual(len(self.other_fw_data), fent.size) > + self.assertEqual(0, fent.flags) > + self.assertEqual(self.other_fw_data, fent.data) > + > + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') > + def test_reader_get_entry(self): > + """Test get_entry() by name and UUID""" > + reader = self.create_fiptool_image() > + fents = reader.fents > + fent = reader.get_entry('fwu') > + self.assertEqual(fent, fents[0]) > + > + fent = reader.get_entry( > + bytes([0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d, > + 0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a])) > + self.assertEqual(fent, fents[1]) > + > + # Try finding entries that don't exist > + with self.assertRaises(Exception) as err: > + fent = reader.get_entry('scp-fwu-cfg') > + self.assertIn("Cannot find FIP entry 'scp-fwu-cfg'", > str(err.exception)) > + > + with self.assertRaises(Exception) as err: > + fent = reader.get_entry(bytes(list(range(16)))) > + self.assertIn( > + "Cannot find FIP entry '00010203-0405-0607-0809-0a0b0c0d0e0f'", > + str(err.exception)) > + > + with self.assertRaises(Exception) as err: > + fent = reader.get_entry('blah') > + self.assertIn("Unknown FIP entry type 'blah'", str(err.exception)) > + > + @unittest.skipIf(not HAVE_FIPTOOL, 'No fiptool available') > + def test_fiptool_errors(self): > + """Check some error reporting from fiptool""" > + with self.assertRaises(Exception) as err: > + with test_util.capture_sys_output(): > + fip_util.fiptool('create', '--fred') > + self.assertIn("Failed to run (error 1): 'fiptool create --fred'", > + str(err.exception)) > + > + > +if __name__ == '__main__': > + unittest.main() > diff --git a/tools/binman/main.py b/tools/binman/main.py > index 8c1e478d54c..1a639f43e9e 100755 > --- a/tools/binman/main.py > +++ b/tools/binman/main.py > @@ -59,6 +59,7 @@ def RunTests(debug, verbosity, processes, > test_preserve_dirs, args, toolpath): > from binman import elf_test > from binman import entry_test > from binman import fdt_test > + from binman import fip_util_test > from binman import ftest > from binman import image_test > import doctest > @@ -72,7 +73,8 @@ def RunTests(debug, verbosity, processes, > test_preserve_dirs, args, toolpath): > result, debug, verbosity, test_preserve_dirs, processes, test_name, > toolpath, > [entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt, > - elf_test.TestElf, image_test.TestImage, cbfs_util_test.TestCbfs]) > + elf_test.TestElf, image_test.TestImage, cbfs_util_test.TestCbfs, > + fip_util_test.TestFip]) > > return test_util.ReportResult('binman', test_name, result) > > -- > 2.34.0.rc2.393.gf8c9666880-goog >