tools/manifest_tools/pfm_generator.py (337 lines of code) (raw):
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT license.
"""
from __future__ import print_function
from __future__ import unicode_literals
import os
import copy
import ctypes
import argparse
import manifest_types
import manifest_common
import pfm_generator_v1
PFM_CONFIG_FILENAME = "pfm_generator.config"
def generate_rw_regions_buf (xml_rw):
"""
Create a buffer of pfm_rw_region struct instances from parsed XML list
:param xml_rw: List of parsed XML of RW regions to be included in PFM
:return RW regions buffer, length of RW regions buffer, number of RW regions, list of all
regions
"""
if xml_rw is None or len (xml_rw) < 1:
return (ctypes.c_ubyte * 0) (), 0, 0, []
class pfm_rw_region (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('rw_flags', ctypes.c_ubyte),
('reserved', ctypes.c_ubyte * 3),
('rw_start_addr', ctypes.c_uint),
('rw_end_addr', ctypes.c_uint)]
num_rw_regions = len (xml_rw)
rw_regions_buf = (ctypes.c_ubyte * (ctypes.sizeof (pfm_rw_region) * num_rw_regions)) ()
rw_regions_len = 0
all_regions = []
reserved_buf = (ctypes.c_ubyte * 3) ()
ctypes.memset (reserved_buf, 0, 3)
for rw_region in xml_rw:
rw_start_addr = int (manifest_common.get_key_from_dict (rw_region, "start",
"RW region start address"), 16)
rw_end_addr = int (manifest_common.get_key_from_dict (rw_region, "end",
"RW region end address"), 16)
manifest_common.check_region_address_validity (rw_start_addr, rw_end_addr)
all_regions.append ([rw_start_addr, rw_end_addr])
rw_flags = int (manifest_common.get_key_from_dict (rw_region, "operation_fail",
"Operation on Fail"), 16)
rw_region_body = pfm_rw_region (rw_flags, reserved_buf, rw_start_addr, rw_end_addr)
rw_regions_len = manifest_common.move_list_to_buffer (rw_regions_buf, rw_regions_len,
[rw_region_body])
return rw_regions_buf, rw_regions_len, num_rw_regions, all_regions
def generate_signed_imgs_buf (xml_signed_imgs):
"""
Create a buffer of pfm_signed_image struct instances from parsed XML list
:param xml_signed_imgs: List of parsed XML of signed images to be included in PFM
:return Signed images buffer, length of signed images buffer, number of signed images, list of
all regions
"""
if xml_signed_imgs is None or len (xml_signed_imgs) < 1:
return None, 0, 0
class pfm_signed_image_header (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('hash_type', ctypes.c_ubyte),
('region_count', ctypes.c_ubyte),
('image_flags', ctypes.c_ubyte),
('reserved', ctypes.c_ubyte)]
class pfm_signed_image_region (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('img_start_addr', ctypes.c_uint),
('img_end_addr', ctypes.c_uint)]
signed_imgs = []
signed_imgs_len = 0
num_signed_imgs = len (xml_signed_imgs)
all_regions = []
for signed_img in xml_signed_imgs:
hash_type = int (manifest_common.get_key_from_dict (signed_img, "hash_type", "Hash type"),
16)
validate = manifest_common.get_key_from_dict (signed_img, "validate", "Validate region")
signed_img_hash = manifest_common.get_key_from_dict (signed_img, "hash",
"Signed image hash")
signed_img_hash_arr = (ctypes.c_ubyte * len (signed_img_hash)).from_buffer_copy (
signed_img_hash)
signed_img_hash_arr_len = ctypes.sizeof (signed_img_hash_arr)
img_flags = 1 if validate == "true" else 0
num_signed_regions = 0
signed_regions_len = 0
if "regions" in signed_img:
signed_regions_buf = (ctypes.c_ubyte * (ctypes.sizeof (pfm_signed_image_region) * \
len (signed_img["regions"]))) ()
for region in signed_img["regions"]:
img_start_addr = int (manifest_common.get_key_from_dict (region, "start",
"Signed image start address"), 16)
img_end_addr = int (manifest_common.get_key_from_dict (region, "end",
"Signed image end address"), 16)
manifest_common.check_region_address_validity (img_start_addr, img_end_addr, False)
all_regions.append ([img_start_addr, img_end_addr])
signed_image_region = pfm_signed_image_region (img_start_addr, img_end_addr)
signed_regions_len = manifest_common.move_list_to_buffer (signed_regions_buf,
signed_regions_len, [signed_image_region])
num_signed_regions += 1
signed_img_buf = (ctypes.c_ubyte * (ctypes.sizeof (pfm_signed_image_header) + \
signed_regions_len + signed_img_hash_arr_len)) ()
signed_img_header = pfm_signed_image_header (hash_type, num_signed_regions, img_flags, 0)
signed_img_len = manifest_common.move_list_to_buffer (signed_img_buf, 0, [signed_img_header,
signed_img_hash_arr, signed_regions_buf])
signed_imgs_len += signed_img_len
signed_imgs.append (signed_img_buf)
signed_imgs_buf = (ctypes.c_ubyte * signed_imgs_len) ()
signed_img_len = manifest_common.move_list_to_buffer (signed_imgs_buf, 0, signed_imgs)
return signed_imgs_buf, signed_imgs_len, len (signed_imgs), all_regions
def generate_permutations (src_list):
"""
Generate all possible permutations of 1 version from each FW type in incoming src_list
:param src_list: List of FW types, with a list of FW versions per FW type, and a list of regions
per FW version
:return A list of all possible permutations
"""
new_list = list (src_list)
fw_type = new_list.pop (0)
total_permutations = []
for version in fw_type:
if not new_list:
total_permutations.append (list (version))
else:
permutations = generate_permutations (new_list)
for permutation in permutations:
permutation.extend (version)
total_permutations.extend (permutations)
return total_permutations
def check_max_rw_sections (all_rw_regions, max_rw_sections):
"""
Ensure RW regions fit into maximum number of RW sections
:param all_rw_regions: All RW regions for each FW type
:param max_rw_sections: Number of non-contiguous RW sections supported
"""
all_rw_permutations = generate_permutations (all_rw_regions)
for permutation in all_rw_permutations:
permutation_copy = copy.deepcopy (permutation)
permutation_copy = sorted (permutation_copy)
for i_region in range (len (permutation_copy) - 1, 0, -1):
region1 = permutation_copy[i_region - 1]
region2 = permutation_copy[i_region]
if manifest_common.check_if_regions_contiguous (region1, region2):
permutation_copy[i_region - 1][1] = permutation_copy[i_region][1]
permutation_copy.remove (permutation_copy[i_region])
if len (permutation_copy) > max_rw_sections:
raise ValueError (
"Number of non-contiguous RW regions greater than maximum defined: {0} vs {1}".format (
len (permutation_copy), max_rw_sections))
def check_overlapping_regions (all_regions, ignore_overlap = False):
"""
Ensure no regions overlap with regions from other FW types. Method gathers all overlapping
regions in a set and displays them at the end of the function. If overlaps are found, method
will halt with a ValueError, unless `ignore_overlap` is True.
:param all_regions: All RW and signed image regions for each FW type
:param ignore_overlap: Bool - Flag to ignore overlapping regions. Will create PFM even if
overlapping regions are found, if set to True.
"""
# Create empty set to store list of overlapping regions.
overlaps = set()
for i_fw_type1 in range (len (all_regions)):
for version1 in all_regions[i_fw_type1]:
for i_region1 in range (len (version1)):
region1 = version1[i_region1]
for i_region2 in range (i_region1 + 1, len (version1)):
region2 = version1[i_region2]
if manifest_common.check_if_regions_overlap (region1, region2):
# add overlap to set
overlaps.add(((region1[0], region1[1]), (region2[0],region2[1])))
for i_fw_type2 in range (i_fw_type1 + 1, len (all_regions)):
for version2 in all_regions[i_fw_type2]:
for region2 in version2:
if manifest_common.check_if_regions_overlap (region1, region2):
# add overlap to set
overlaps.add(((region1[0], region1[1]), (region2[0],region2[1])))
# Output warning message if ignore_overlap flag is set, and there are actual overlaps found.
if ignore_overlap and overlaps:
print ()
print ()
print (" *** Warning: ignore_overlap flag is set to TRUE. ***")
print (" *** Overlapping regions will be checked for, but ignored. ***")
print (" *** Overlapping Flash regions can cause Cerberus Flash Verification to fail when in active mode. ***")
print (" *** PFM will be generated anyway. See logs below for potential overlaps. ***")
print ()
print ()
# Output overlap message for each overlap found
for overlap in overlaps:
regionA = overlap[0]
regionB = overlap[1]
message = "Region at [0x{0}:0x{1}] overlapping with region at [0x{2}:0x{3}]".format (
format (regionA[0], 'x'), format (regionA[1], 'x'),
format (regionB[0], 'x'), format (regionB[1], 'x')
)
print(message)
# Halt if ignore_overlap flag is not set
if overlaps and not ignore_overlap:
raise ValueError ("Overlapping Regions have been found. Halting!")
def generate_fw_versions_list (xml_list, hash_engine, max_rw_sections, ignore_overlap = False):
"""
Create a list of FW version struct instances for each FW type from parsed XML list
:param xml_list: List of parsed XML of FW versions to be included in PFM
:param hash_engine: Hashing engine
:param max_rw_sections: Maximum number of non-contiguous RW sections supported
:return FW buffer, number of FW, list of FW TOC entries, list of FW hashes, Unused byte
"""
if xml_list is None or len (xml_list) < 1:
return None, 0, None, None, 0
fw_version_list = {}
runtime_update_list = {}
unused_byte = None
all_regions = {}
all_rw_regions = {}
for filename, xml in xml_list.items():
fw_type = manifest_common.get_key_from_dict (xml, "fw_type", "FW Type")
manifest_common.check_maximum (len (fw_type), 255, "FW type {0} string length".format (
fw_type))
if fw_type not in fw_version_list:
fw_version_list[fw_type] = dict ()
all_regions[fw_type] = []
all_rw_regions[fw_type] = []
unused_byte_val = int (manifest_common.get_key_from_dict (xml, "unused_byte",
"Unused Byte"), 16)
manifest_common.check_maximum (unused_byte_val, 255, "Unused byte")
if unused_byte is None:
unused_byte = unused_byte_val
else:
if unused_byte_val != unused_byte:
raise ValueError ("Different unused byte values found: ({0}) vs ({1}) - {2}".format (
unused_byte_val, unused_byte, filename))
runtime_update_val = manifest_common.get_key_from_dict (xml, "runtime_update",
"Runtime Update")
if fw_type not in runtime_update_list:
runtime_update_list[fw_type] = runtime_update_val
else:
if runtime_update_list[fw_type] != runtime_update_val:
raise ValueError (
"Different runtime update values found for FW type ({0}): ({1}) vs ({2}) - {3}".format (
fw_type, runtime_update_val, runtime_update_list[fw_type], filename))
version_addr = int (manifest_common.get_key_from_dict (xml, "version_addr",
"Version Address"), 16)
version_id = manifest_common.get_key_from_dict (xml, "version_id", "Version ID")
version_id_len = len (version_id)
manifest_common.check_maximum (version_id_len, 255, "Version ID {0} length".format (
version_id))
padding, padding_len = manifest_common.generate_4byte_padding_buf (version_id_len)
all_regions_list = []
if "rw_regions" in xml:
rw_regions_buf, rw_regions_len, num_rw_regions, rw_regions = generate_rw_regions_buf (
xml["rw_regions"])
all_regions_list.extend (rw_regions)
all_rw_regions[fw_type].append (rw_regions)
if "signed_imgs" in xml:
signed_imgs_buf, signed_imgs_len, num_signed_imgs, signed_regions = \
generate_signed_imgs_buf (xml["signed_imgs"])
all_regions_list.extend (signed_regions)
all_regions[fw_type].append (all_regions_list)
class pfm_fw_version (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('image_count', ctypes.c_ubyte),
('rw_count', ctypes.c_ubyte),
('version_length', ctypes.c_ubyte),
('reserved', ctypes.c_ubyte),
('version_addr', ctypes.c_uint),
('version_id', ctypes.c_char * version_id_len),
('version_id_padding', ctypes.c_ubyte * padding_len),
('rw_regions', ctypes.c_ubyte * rw_regions_len),
('signed_imgs', ctypes.c_ubyte * signed_imgs_len)]
fw_version = pfm_fw_version (num_signed_imgs, num_rw_regions, version_id_len, 0,
version_addr, version_id.encode ('utf-8'), padding, rw_regions_buf, signed_imgs_buf)
for prev_version_id, prev_fw_version in fw_version_list[fw_type].items ():
if prev_version_id == version_id:
raise KeyError (
"Failed to generate PFM: Duplicate version ID - {0} in FW type {1}".format (
version_id, fw_type))
elif prev_version_id.startswith(version_id) or version_id.startswith(prev_version_id):
raise ValueError (
"Failed to generate PFM: Ambiguous version ID - {0}, {1} in FW type {2}".format (
prev_version_id, version_id, fw_type))
fw_version_list[fw_type].update ({version_id: fw_version})
all_regions_list = []
all_rw_regions_list = []
for fw_id, fw_id_list in all_regions.items():
all_regions_list.append (fw_id_list)
for fw_id, fw_id_list in all_rw_regions.items():
all_rw_regions_list.append (fw_id_list)
check_overlapping_regions (all_regions_list, ignore_overlap)
check_overlapping_regions (all_rw_regions_list, ignore_overlap)
check_max_rw_sections (all_rw_regions_list, max_rw_sections)
return fw_version_list, runtime_update_list, unused_byte
def generate_fw_buf (xml_list, hash_engine, max_rw_sections, ignore_overlap = False):
"""
Create a buffer of FW struct instances from parsed XML list
:param xml_list: List of parsed XML of FW to be included in PFM
:param hash_engine: Hashing engine
:param max_rw_sections: Maximum number of non-contiguous RW sections supported
:return FW buffer, number of FW, list of FW TOC entries, list of FW hashes, Unused byte
"""
if xml_list is None or len (xml_list) < 1:
return None, 0, None, None, 0
fw_list = []
fw_toc_list = []
fw_hash_list = []
num_fw = 0
fw_len = 0
fw_version_list, runtime_update_list, unused_byte = generate_fw_versions_list (xml_list,
hash_engine, max_rw_sections, ignore_overlap)
for fw_id, fw_versions in fw_version_list.items ():
fw_id_len = len (fw_id)
manifest_common.check_maximum (fw_id_len, 255, "FW ID {0} length".format (fw_id))
padding, padding_len = manifest_common.generate_4byte_padding_buf (fw_id_len)
class pfm_fw (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('version_count', ctypes.c_ubyte),
('fw_id_length', ctypes.c_ubyte),
('fw_flags', ctypes.c_ubyte),
('reserved', ctypes.c_ubyte),
('fw_id', ctypes.c_char * fw_id_len),
('fw_id_padding', ctypes.c_ubyte * padding_len)]
fw_flags = 0 if runtime_update_list[fw_id] == "false" else 1
fw = pfm_fw (len (fw_versions), fw_id_len, fw_flags, 0, fw_id.encode ('utf-8'), padding)
fw_toc_entry = manifest_common.manifest_toc_entry (manifest_common.PFM_V2_FW_TYPE_ID,
manifest_common.V2_BASE_TYPE_ID, 1, 0, 0, ctypes.sizeof (fw))
fw_hash = manifest_common.generate_hash (fw, hash_engine)
fw_list.append (fw)
fw_toc_list.append (fw_toc_entry)
fw_hash_list.append (fw_hash)
fw_len += ctypes.sizeof (fw)
for version_id, fw_version in fw_versions.items ():
fw_list.append (fw_version)
fw_len += ctypes.sizeof (fw_version)
fw_version_toc_entry = manifest_common.manifest_toc_entry (
manifest_common.PFM_V2_FW_VERSION_TYPE_ID, manifest_common.PFM_V2_FW_TYPE_ID, 1,
0, 0, ctypes.sizeof (fw_version))
fw_toc_list.append (fw_version_toc_entry)
fw_version_hash = manifest_common.generate_hash (fw_version, hash_engine)
fw_hash_list.append (fw_version_hash)
num_fw += 1
fw_buffer = (ctypes.c_ubyte * fw_len) ()
fw_buffer_len = manifest_common.move_list_to_buffer (fw_buffer, 0, fw_list)
return fw_buffer, num_fw, fw_toc_list, fw_hash_list, unused_byte
def generate_flash_device_buf (hash_engine, unused_byte, fw_count):
"""
Create a buffer of FW struct instances from parsed XML list
:param hash_engine: Hashing engine
:param unused_byte: Unused byte
:param fw_count: Number of FW types in flash device
:return Flash device buffer, Flash device TOC entry, Flash device hash
"""
class pfm_flash_device (ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [('blank_byte', ctypes.c_ubyte),
('fw_count', ctypes.c_ubyte),
('reserved', ctypes.c_ushort)]
flash_device = pfm_flash_device (unused_byte, fw_count, 0)
flash_device_toc_entry = manifest_common.manifest_toc_entry (
manifest_common.PFM_V2_FLASH_DEVICE_TYPE_ID, manifest_common.V2_BASE_TYPE_ID, 0, 0, 0,
ctypes.sizeof (flash_device))
flash_device_hash = manifest_common.generate_hash (flash_device, hash_engine)
return flash_device, flash_device_toc_entry, flash_device_hash
#*************************************** Start of Script ***************************************
default_config = os.path.join (os.path.dirname (os.path.abspath (__file__)), PFM_CONFIG_FILENAME)
parser = argparse.ArgumentParser (description = 'Create a PFM')
parser.add_argument ('config', nargs = '?', default = default_config,
help = 'Path to configuration file')
parser.add_argument ('--bypass', action = 'store_true', help = 'Create a bypass mode PFM')
parser.add_argument ('--ignore_overlap', action = 'store_true',
help = 'Warn on PFM region overlaps, but output PFM anyway.')
args = parser.parse_args ()
processed_xml, sign, key_size, key, key_type, hash_type, pfm_id, output, xml_version, empty, \
max_rw_sections, selection_list, component_map, component_map_file = \
manifest_common.load_xmls (args.config, None, manifest_types.PFM)
if xml_version == manifest_types.VERSION_2:
elements_list = []
toc_list = []
hash_list = []
hash_engine = manifest_common.get_hash_engine (hash_type)
platform_id = manifest_common.get_platform_id_from_xml_list (processed_xml)
platform_id, platform_id_toc_entry, platform_id_hash = \
manifest_common.generate_platform_id_buf ({"platform_id": platform_id}, hash_engine)
pfm_len = ctypes.sizeof (platform_id)
elements_list.append (platform_id)
toc_list.append (platform_id_toc_entry)
hash_list.append (platform_id_hash)
if not args.bypass:
fw, num_fw, fw_toc_entries, fw_hashes, unused_byte = generate_fw_buf (processed_xml,
hash_engine, max_rw_sections, args.ignore_overlap)
flash_device, flash_device_toc_entry, flash_device_hash = generate_flash_device_buf (
hash_engine, unused_byte, num_fw)
pfm_len += ctypes.sizeof (flash_device)
elements_list.append (flash_device)
toc_list.append (flash_device_toc_entry)
hash_list.append (flash_device_hash)
pfm_len += ctypes.sizeof (fw)
elements_list.append (fw)
toc_list.extend (fw_toc_entries)
hash_list.extend (fw_hashes)
manifest_common.generate_manifest (hash_engine, hash_type, pfm_id, manifest_types.PFM,
xml_version, sign, key, key_size, key_type, toc_list, hash_list, elements_list, pfm_len,
output)
else:
pfm_generator_v1.generate_v1_pfm (pfm_id, key_size, hash_type, key_type, processed_xml,
args.bypass, sign, key, output)
print ("Completed PFM generation: {0}".format (output))