Initial version of pldm_fwup_pkg_creator.py
pldm_fwup_pkg_creator.py is a python script that can generate a PLDM
firmware update package out of one or more image files, given a
corresponding metadata JSON file.
Signed-off-by: Deepak Kodihalli <deepak.kodihalli.83@gmail.com>
Change-Id: I7655ab2be72731332afc686b1c55b1312455ba1a
Signed-off-by: Tom Joseph <rushtotom@gmail.com>
diff --git a/tools/fw-update/README.md b/tools/fw-update/README.md
new file mode 100644
index 0000000..d231a1f
--- /dev/null
+++ b/tools/fw-update/README.md
@@ -0,0 +1,127 @@
+# Overview
+pldm_fwup_pkg_creator.py is a python script that can package one or more
+firmware image blobs into a PLDM firmware update package, as per the DSP0267
+specification v1.1.0 - Section 7.
+
+# Requirements
+- Python 3.6+
+- Python bitarray module
+ - For example on Ubuntu: sudo pip3 install bitarray
+
+# Usage
+
+ pldm_fwup_pkg_creator.py [-h]
+ pldmfwuppkgname metadatafile images
+ [images ...]
+
+ positional arguments:
+ pldmfwuppkgname Name of the PLDM FW update package
+ metadatafile Path of metadata JSON file
+ images One or more firmware image paths, in the same order as
+ ComponentImageInformationArea entries
+
+- Provide name for the PLDM FW update package, output will be written to a file
+with this name
+- Providing the metadata JSON file is a must
+- The file path of at least one image file must be provided
+- In case there are more than one images, they should be specified in the
+*same order* as the entries in the "ComponentImageInformationArea" list in the
+metadata json
+
+# Metadata JSON file
+Some fields corresponding to the PLDM firmware update package must be
+specified in an input metadata file (see the section 'Mapping fields to
+DSP0267') that's in JSON format. Typically it is not necessary to write this
+file each time the PLDM firmware update package has to be generated - one or
+more platform specific metadata JSON files can be reused. The key names used in
+the metadata JSON *match with the proprerty names* used in DSP0267.
+There is an example metadata json file included - metadata-example.json, which
+has example entries for preparing a PLDM firmware update package that targets
+three devices, and there are a total of three component images.
+
+# Mapping fields to DSP0267
+This section describes the following:
+- which fields of the PLDM firmware update package are supported by the script
+- whether those fields need to be specified in the JSON, or are generated by the
+script
+
+## Package Header Information
+Metadata JSON must have a key called "PackageHeaderInformation", which is of
+type Object. See below for details on properties:
+- PackageHeaderIdentifier: Supported, must be specified in metadata file
+- PackageHeaderFormatRevision: Supported, must be specified in metadata file
+- PackageHeaderSize: Supported, generated by the script
+- PackageReleaseDateTime: Supported, this is an optional field in the metadata
+file and the format is dd/MM/yyyy HH:mm:ss. If not specified in the metadata
+file, current time is taken.
+- ComponentBitmapBitLength: Supported, generated by the script
+ - Only 32 components supported at the moment
+- PackageVersionStringType: Supported - only ASCII at the moment. Generated by
+the script
+- PackageVersionStringLength: Supported, generated by the script
+- PackageVersionString: Supported, must be specified in metadata file
+
+## Firmware Device Identification Area
+Metadata JSON must have a key called "FirmwareDeviceIdentificationArea", which
+is of type List. Each List entry corresponds to an firmware device ID record.
+See below for details on properties:
+- DeviceIDRecordCount: Supported, generated by the script
+- FirmwareDeviceIDRecords: which is a list of Individual Firmware Device ID
+Records. Each such record can have the following properties:
+ - RecordLength: Supported, generated by the script
+ - DescriptorCount: Supported, generated by the script
+ - DeviceUpdateOptionFlags: Supported, must be specified in metadata file
+ - add each bit that is to be set to 1 to the list
+ "DeviceUpdateOptionFlags"
+ - ComponentImageSetVersionStringType: Supported - only ASCII at the moment.
+ Generated by the script
+ - ComponentImageSetVersionStringLength: Supported, generated by the script
+ - FirmwareDevicePackageDataLength: Not supported. Set to 0 by the script
+ - ApplicableComponents: Supported, must be specified in metadata file
+ - specify all "ComponentIdentifier" values that apply in the
+ "ApplicableComponents" list
+ - ComponentImageSetVersionString: Supported, must be specified in metadata
+ file
+ - RecordDescriptors:
+ - Initial Descriptor: Supported, must be specified in metadata file.
+ Metatdata file must have key called "InitialDescriptor", which is of
+ type Object:
+ - InitialDescriptorType: Supported, must be specified in metadata
+ file. The supported types are 0x0000(PCI Vendor ID), 0x0001(IANA
+ Enterprise ID), 0x0002 (UUID), 0x0003(PnP Vendor ID), 0x0004( ACPI
+ Vendor ID)
+ - InitialDescriptorLength: Supported, generated by the script
+ - InitialDescriptorData: Supported, must be specified in metadata
+ file as a hex string
+ - Optional Additional Descriptors: Not supported at the moment
+ - FirmwareDevicePackageData: Not supported at the moment
+
+## Downstream Device Identification Area
+Not supported at the moment
+
+## Component Image Information Area
+Metadata JSON must have a key called "ComponentImageInformationArea", which
+is of type List. Each List entry corresponds to an Individual Component Image
+Information. See below for details on properties:
+- ComponentImageCount: Supported, generated by the script
+- ComponentImageInformation: which is a list of Individual Component Image
+Information records. Each such record can have the following properties:
+ - ComponentClassification: Supported, must be specified in metadata file
+ - ComponentIdentifier: Supported, must be specified in metadata file
+ - ComponentComparisonStamp: Not supported. Set to 0xFFFFFFFF by the script
+ - ComponentOptions: Supported, must be specified in metadata file
+ - add each bit that is to be set to 1 to the list "ComponentOptions"
+ - only supported option at the moment is Force Update (0x0)
+ - RequestedComponentActivationMethod: Supported, must be specified in
+ metadata file
+ - add each bit that is to be set to 1 to the list
+ "RequestedComponentActivationMethod"
+ - ComponentLocationOffset: Supported, generated by the script
+ - ComponentSize: Supported, generated by the script
+ - ComponentVersionStringType: Supported - only ASCII at the moment.
+ Generated by the script
+ - ComponentVersionStringLength: Supported, generated by the script
+ - ComponentVersionString: Supported, must be specified in metadata file
+
+## Package Header Checksum
+Supported, generated by the script
diff --git a/tools/fw-update/metadata-example.json b/tools/fw-update/metadata-example.json
new file mode 100644
index 0000000..70751ad
--- /dev/null
+++ b/tools/fw-update/metadata-example.json
@@ -0,0 +1,83 @@
+{
+ "PackageHeaderInformation": {
+ "PackageHeaderIdentifier": "1244D2648D7D4718A030FC8A56587D5A",
+ "PackageHeaderFormatVersion": 2,
+ "PackageReleaseDateTime": "25/12/2021 00:00:00",
+ "PackageVersionString": "VersionString1"
+ },
+ "FirmwareDeviceIdentificationArea": [
+ {
+ "DeviceUpdateOptionFlags": [
+ 0
+ ],
+ "ComponentImageSetVersionString": "VersionString2",
+ "ApplicableComponents": [
+ 100,
+ 200
+ ],
+ "InitialDescriptor": {
+ "InitialDescriptorType": 2,
+ "InitialDescriptorData": "1244D2648D7D4718A030FC8A56587D5B"
+ }
+ },
+ {
+ "DeviceUpdateOptionFlags": [
+ ],
+ "ComponentImageSetVersionString": "VersionString3",
+ "ApplicableComponents": [
+ 100,
+ 200,
+ 300
+ ],
+ "InitialDescriptor": {
+ "InitialDescriptorType": 2,
+ "InitialDescriptorData": "1244D2648D7D4718A030FC8A56587D5C"
+ }
+ },
+ {
+ "DeviceUpdateOptionFlags": [
+ ],
+ "ComponentImageSetVersionString": "VersionString4",
+ "ApplicableComponents": [
+ 100
+ ],
+ "InitialDescriptor": {
+ "InitialDescriptorType": 2,
+ "InitialDescriptorData": "1244D2648D7D4718A030FC8A56587D5D"
+ }
+ }
+ ],
+ "ComponentImageInformationArea": [
+ {
+ "ComponentClassification": 10,
+ "ComponentIdentifier": 100,
+ "ComponentOptions": [
+ ],
+ "RequestedComponentActivationMethod": [
+ ],
+ "ComponentVersionString": "VersionString5"
+ },
+ {
+ "ComponentClassification": 10,
+ "ComponentIdentifier": 200,
+ "ComponentOptions": [
+ ],
+ "RequestedComponentActivationMethod": [
+ 0
+ ],
+ "ComponentVersionString": "VersionString6"
+ },
+ {
+ "ComponentClassification": 16,
+ "ComponentIdentifier": 300,
+ "ComponentOptions": [
+ 0
+ ],
+ "RequestedComponentActivationMethod": [
+ 2,
+ 3
+ ],
+ "ComponentVersionString": "VersionString7"
+ }
+ ]
+}
diff --git a/tools/fw-update/pldm_fwup_pkg_creator.py b/tools/fw-update/pldm_fwup_pkg_creator.py
new file mode 100755
index 0000000..3eb3ecc
--- /dev/null
+++ b/tools/fw-update/pldm_fwup_pkg_creator.py
@@ -0,0 +1,497 @@
+#!/usr/bin/env python3
+
+"""Script to create PLDM FW update package"""
+
+import argparse
+import binascii
+from datetime import datetime
+import json
+import os
+import struct
+import sys
+
+import math
+from bitarray import bitarray
+from bitarray.util import ba2int
+
+string_types = dict([
+ ("Unknown", 0),
+ ("ASCII", 1),
+ ("UTF8", 2),
+ ("UTF16", 3),
+ ("UTF16LE", 4),
+ ("UTF16BE", 5)])
+
+descriptor_type_name_length = {
+ 0x0000: ["PCI Vendor ID", 2],
+ 0x0001: ["IANA Enterprise ID", 4],
+ 0x0002: ["UUID", 16],
+ 0x0003: ["PnP Vendor ID", 3],
+ 0x0004: ["ACPI Vendor ID", 4]}
+
+
+def check_string_length(string):
+ """Check if the length of the string is not greater than 255."""
+ if len(string) > 255:
+ sys.exit("ERROR: Max permitted string length is 255")
+
+
+def write_pkg_release_date_time(pldm_fw_up_pkg, release_date_time):
+ '''
+ Write the timestamp into the package header. The timestamp is formatted as
+ series of 13 bytes defined in DSP0240 specification.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ release_date_time: Package Release Date Time
+ '''
+ time = release_date_time.time()
+ date = release_date_time.date()
+ us_bytes = time.microsecond.to_bytes(3, byteorder='little')
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ '<hBBBBBBBBHB',
+ 0,
+ us_bytes[0],
+ us_bytes[1],
+ us_bytes[2],
+ time.second,
+ time.minute,
+ time.hour,
+ date.day,
+ date.month,
+ date.year,
+ 0))
+
+
+def write_package_version_string(pldm_fw_up_pkg, metadata):
+ '''
+ Write PackageVersionStringType, PackageVersionStringLength and
+ PackageVersionString to the package header.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ metadata: metadata about PLDM FW update package
+ '''
+ # Hardcoded string type to ASCII
+ string_type = string_types["ASCII"]
+ package_version_string = \
+ metadata["PackageHeaderInformation"]["PackageVersionString"]
+ check_string_length(package_version_string)
+ format_string = '<BB' + str(len(package_version_string)) + 's'
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ format_string,
+ string_type,
+ len(package_version_string),
+ package_version_string.encode('ascii')))
+
+
+def write_component_bitmap_bit_length(pldm_fw_up_pkg, metadata):
+ '''
+ ComponentBitmapBitLength in the package header indicates the number of bits
+ that will be used represent the bitmap in the ApplicableComponents field
+ for a matching device. The value shall be a multiple of 8 and be large
+ enough to contain a bit for each component in the package. The number of
+ components in the JSON file is used to populate the bitmap length.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ metadata: metadata about PLDM FW update package
+
+ Returns:
+ ComponentBitmapBitLength: number of bits that will be used
+ represent the bitmap in the ApplicableComponents field for a
+ matching device
+ '''
+ # The script supports upto 32 components now
+ max_components = 32
+ bitmap_multiple = 8
+
+ num_components = len(metadata["ComponentImageInformationArea"])
+ if num_components > max_components:
+ sys.exit("ERROR: only upto 32 components supported now")
+ component_bitmap_bit_length = bitmap_multiple * \
+ math.ceil(num_components/bitmap_multiple)
+ pldm_fw_up_pkg.write(struct.pack('<H', int(component_bitmap_bit_length)))
+ return component_bitmap_bit_length
+
+
+def write_pkg_header_info(pldm_fw_up_pkg, metadata):
+ '''
+ ComponentBitmapBitLength in the package header indicates the number of bits
+ that will be used represent the bitmap in the ApplicableComponents field
+ for a matching device. The value shall be a multiple of 8 and be large
+ enough to contain a bit for each component in the package. The number of
+ components in the JSON file is used to populate the bitmap length.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ metadata: metadata about PLDM FW update package
+
+ Returns:
+ ComponentBitmapBitLength: number of bits that will be used
+ represent the bitmap in the ApplicableComponents field for a
+ matching device
+ '''
+ uuid = metadata["PackageHeaderInformation"]["PackageHeaderIdentifier"]
+ package_header_identifier = bytearray.fromhex(uuid)
+ pldm_fw_up_pkg.write(package_header_identifier)
+
+ package_header_format_revision = \
+ metadata["PackageHeaderInformation"]["PackageHeaderFormatVersion"]
+ # Size will be computed and updated subsequently
+ package_header_size = 0
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ '<BH',
+ package_header_format_revision,
+ package_header_size))
+
+ try:
+ release_date_time = datetime.strptime(
+ metadata["PackageHeaderInformation"]["PackageReleaseDateTime"],
+ "%d/%m/%Y %H:%M:%S")
+ write_pkg_release_date_time(pldm_fw_up_pkg, release_date_time)
+ except KeyError:
+ write_pkg_release_date_time(pldm_fw_up_pkg, datetime.now())
+
+ component_bitmap_bit_length = write_component_bitmap_bit_length(
+ pldm_fw_up_pkg, metadata)
+ write_package_version_string(pldm_fw_up_pkg, metadata)
+ return component_bitmap_bit_length
+
+
+def get_applicable_components(device, components, component_bitmap_bit_length):
+ '''
+ This function figures out the components applicable for the device and sets
+ the ApplicableComponents bitfield accordingly.
+
+ Parameters:
+ device: device information
+ components: list of components in the package
+ component_bitmap_bit_length: length of the ComponentBitmapBitLength
+
+ Returns:
+ The ApplicableComponents bitfield
+ '''
+ applicable_components_list = device["ApplicableComponents"]
+ applicable_components = bitarray(component_bitmap_bit_length,
+ endian='little')
+ applicable_components.setall(0)
+ for component in components:
+ if component["ComponentIdentifier"] in applicable_components_list:
+ applicable_components[components.index(component)] = 1
+ return applicable_components
+
+
+def write_fw_device_identification_area(pldm_fw_up_pkg, metadata,
+ component_bitmap_bit_length):
+ '''
+ Write firmware device ID records into the PLDM package header
+
+ This function writes the DeviceIDRecordCount and the
+ FirmwareDeviceIDRecords into the firmware update package by processing the
+ metadata JSON. Currently there is no support for optional
+ FirmwareDevicePackageData and for Additional descriptors.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ metadata: metadata about PLDM FW update package
+ component_bitmap_bit_length: length of the ComponentBitmapBitLength
+ '''
+ # The spec limits the number of firmware device ID records to 255
+ max_device_id_record_count = 255
+ devices = metadata["FirmwareDeviceIdentificationArea"]
+ device_id_record_count = len(devices)
+ if device_id_record_count > max_device_id_record_count:
+ sys.exit(
+ "ERROR: there can be only upto 255 entries in the \
+ FirmwareDeviceIdentificationArea section")
+
+ # DeviceIDRecordCount
+ pldm_fw_up_pkg.write(struct.pack('<B', device_id_record_count))
+
+ for device in devices:
+ # RecordLength size
+ record_length = 2
+
+ # Only initial descriptor type supported now
+ descriptor_count = 1
+ record_length += 1
+
+ # DeviceUpdateOptionFlags
+ device_update_option_flags = bitarray(32, endian='little')
+ device_update_option_flags.setall(0)
+ # Continue component updates after failure
+ supported_device_update_option_flags = [0]
+ for option in device["DeviceUpdateOptionFlags"]:
+ if option not in supported_device_update_option_flags:
+ sys.exit("ERROR: unsupported DeviceUpdateOptionFlag entry")
+ device_update_option_flags[option] = 1
+ record_length += 4
+
+ # ComponentImageSetVersionStringType supports only ASCII for now
+ component_image_set_version_string_type = string_types["ASCII"]
+ record_length += 1
+
+ # ComponentImageSetVersionStringLength
+ component_image_set_version_string = \
+ device["ComponentImageSetVersionString"]
+ check_string_length(component_image_set_version_string)
+ record_length += len(component_image_set_version_string)
+ record_length += 1
+
+ # Optional FirmwareDevicePackageData not supported now,
+ # FirmwareDevicePackageDataLength is set to 0x0000
+ fw_device_pkg_data_length = 0
+ record_length += 2
+
+ # ApplicableComponents
+ components = metadata["ComponentImageInformationArea"]
+ applicable_components = \
+ get_applicable_components(device,
+ components,
+ component_bitmap_bit_length)
+ applicable_components_bitfield_length = \
+ round(len(applicable_components)/8)
+ record_length += applicable_components_bitfield_length
+
+ initial_descriptor = device["InitialDescriptor"]
+ initial_descriptor_type = initial_descriptor["InitialDescriptorType"]
+ initial_descriptor_data = initial_descriptor["InitialDescriptorData"]
+
+ # InitialDescriptorType
+ if descriptor_type_name_length.get(initial_descriptor_type) is None:
+ sys.exit("ERROR: Initial descriptor type not supported")
+ record_length += 2
+
+ # InitialDescriptorLength
+ initial_descriptor_length = \
+ len(bytearray.fromhex(initial_descriptor_data))
+ if initial_descriptor_length != \
+ descriptor_type_name_length.get(initial_descriptor_type)[1]:
+ err_string = "ERROR: Initial descriptor type - " + \
+ descriptor_type_name_length.get(initial_descriptor_type)[0] + \
+ " length is incorrect"
+ sys.exit(err_string)
+ record_length += 2
+
+ # InitialDescriptorData, the byte order in the JSON is retained.
+ record_length += initial_descriptor_length
+
+ format_string = '<HBIBBH' + \
+ str(applicable_components_bitfield_length) + 's' + \
+ str(len(component_image_set_version_string)) + 'sHH'
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ format_string,
+ record_length,
+ descriptor_count,
+ ba2int(device_update_option_flags),
+ component_image_set_version_string_type,
+ len(component_image_set_version_string),
+ fw_device_pkg_data_length,
+ applicable_components.tobytes(),
+ component_image_set_version_string.encode('ascii'),
+ initial_descriptor_type,
+ initial_descriptor_length))
+ pldm_fw_up_pkg.write(bytearray.fromhex(initial_descriptor_data))
+
+
+def write_component_image_info_area(pldm_fw_up_pkg, metadata, image_files):
+ '''
+ Write component image information area into the PLDM package header
+
+ This function writes the ComponentImageCount and the
+ ComponentImageInformation into the firmware update package by processing
+ the metadata JSON. Currently there is no support for
+ ComponentComparisonStamp field and the component option use component
+ comparison stamp.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ metadata: metadata about PLDM FW update package
+ image_files: component images
+ '''
+ components = metadata["ComponentImageInformationArea"]
+ # ComponentImageCount
+ pldm_fw_up_pkg.write(struct.pack('<H', len(components)))
+ component_location_offsets = []
+ # ComponentLocationOffset position in individual component image
+ # information
+ component_location_offset_pos = 12
+
+ for component in components:
+ # Record the location of the ComponentLocationOffset to be updated
+ # after appending images to the firmware update package
+ component_location_offsets.append(pldm_fw_up_pkg.tell() +
+ component_location_offset_pos)
+
+ # ComponentClassification
+ component_classification = component["ComponentClassification"]
+ if component_classification < 0 or component_classification > 0xFFFF:
+ sys.exit(
+ "ERROR: ComponentClassification should be [0x0000 - 0xFFFF]")
+
+ # ComponentIdentifier
+ component_identifier = component["ComponentIdentifier"]
+ if component_identifier < 0 or component_identifier > 0xFFFF:
+ sys.exit(
+ "ERROR: ComponentIdentifier should be [0x0000 - 0xFFFF]")
+
+ # ComponentComparisonStamp not supported
+ component_comparison_stamp = 0xFFFFFFFF
+
+ # ComponentOptions
+ component_options = bitarray(16, endian='little')
+ component_options.setall(0)
+ supported_component_options = [0]
+ for option in component["ComponentOptions"]:
+ if option not in supported_component_options:
+ sys.exit(
+ "ERROR: unsupported ComponentOption in\
+ ComponentImageInformationArea section")
+ component_options[option] = 1
+
+ # RequestedComponentActivationMethod
+ requested_component_activation_method = bitarray(16, endian='little')
+ requested_component_activation_method.setall(0)
+ supported_requested_component_activation_method = [0, 1, 2, 3, 4, 5]
+ for option in component["RequestedComponentActivationMethod"]:
+ if option not in supported_requested_component_activation_method:
+ sys.exit(
+ "ERROR: unsupported RequestedComponent\
+ ActivationMethod entry")
+ requested_component_activation_method[option] = 1
+
+ # ComponentLocationOffset
+ component_location_offset = 0
+ # ComponentSize
+ component_size = 0
+ # ComponentVersionStringType
+ component_version_string_type = string_types["ASCII"]
+ # ComponentVersionStringlength
+ # ComponentVersionString
+ component_version_string = component["ComponentVersionString"]
+ check_string_length(component_version_string)
+
+ format_string = '<HHIHHIIBB' + str(len(component_version_string)) + 's'
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ format_string,
+ component_classification,
+ component_identifier,
+ component_comparison_stamp,
+ ba2int(component_options),
+ ba2int(requested_component_activation_method),
+ component_location_offset,
+ component_size,
+ component_version_string_type,
+ len(component_version_string),
+ component_version_string.encode('ascii')))
+
+ index = 0
+ pkg_header_checksum_size = 4
+ start_offset = pldm_fw_up_pkg.tell() + pkg_header_checksum_size
+ # Update ComponentLocationOffset and ComponentSize for all the components
+ for offset in component_location_offsets:
+ file_size = os.stat(image_files[index]).st_size
+ pldm_fw_up_pkg.seek(offset)
+ pldm_fw_up_pkg.write(
+ struct.pack(
+ '<II', start_offset, file_size))
+ start_offset += file_size
+ index += 1
+ pldm_fw_up_pkg.seek(0, os.SEEK_END)
+
+
+def write_pkg_header_checksum(pldm_fw_up_pkg):
+ '''
+ Write PackageHeaderChecksum into the PLDM package header.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ '''
+ pldm_fw_up_pkg.seek(0)
+ package_header_checksum = binascii.crc32(pldm_fw_up_pkg.read())
+ pldm_fw_up_pkg.seek(0, os.SEEK_END)
+ pldm_fw_up_pkg.write(struct.pack('<I', package_header_checksum))
+
+
+def update_pkg_header_size(pldm_fw_up_pkg):
+ '''
+ Update PackageHeader in the PLDM package header. The package header size
+ which is the count of all bytes in the PLDM package header structure is
+ calculated once the package header contents is complete.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ '''
+ file_size = pldm_fw_up_pkg.tell()
+ pkg_header_size_offset = 17
+ # Seek past PackageHeaderIdentifier and PackageHeaderFormatRevision
+ pldm_fw_up_pkg.seek(pkg_header_size_offset)
+ pldm_fw_up_pkg.write(struct.pack('<H', file_size))
+ pldm_fw_up_pkg.seek(0, os.SEEK_END)
+
+
+def append_component_images(pldm_fw_up_pkg, image_files):
+ '''
+ Append the component images to the firmware update package.
+
+ Parameters:
+ pldm_fw_up_pkg: PLDM FW update package
+ image_files: component images
+ '''
+ for image in image_files:
+ with open(image, 'rb') as file:
+ for line in file:
+ pldm_fw_up_pkg.write(line)
+
+
+def main():
+ """Create PLDM FW update (DSP0267) package based on a JSON metadata file"""
+ parser = argparse.ArgumentParser()
+ parser.add_argument("pldmfwuppkgname",
+ help="Name of the PLDM FW update package")
+ parser.add_argument("metadatafile", help="Path of metadata JSON file")
+ parser.add_argument(
+ "images", nargs='+',
+ help="One or more firmware image paths, in the same order as\
+ ComponentImageInformationArea entries")
+
+ args = parser.parse_args()
+ image_files = args.images
+ with open(args.metadatafile) as file:
+ try:
+ metadata = json.load(file)
+ except ValueError:
+ sys.exit("ERROR: Invalid metadata JSON file")
+
+ # Validate the number of component images
+ if len(image_files) != len(metadata["ComponentImageInformationArea"]):
+ sys.exit("ERROR: number of images passed != number of entries \
+ in ComponentImageInformationArea")
+
+ try:
+ with open(args.pldmfwuppkgname, 'w+b') as pldm_fw_up_pkg:
+ component_bitmap_bit_length = write_pkg_header_info(pldm_fw_up_pkg,
+ metadata)
+ write_fw_device_identification_area(pldm_fw_up_pkg,
+ metadata,
+ component_bitmap_bit_length)
+ write_component_image_info_area(pldm_fw_up_pkg, metadata,
+ image_files)
+ write_pkg_header_checksum(pldm_fw_up_pkg)
+ update_pkg_header_size(pldm_fw_up_pkg)
+ append_component_images(pldm_fw_up_pkg, image_files)
+ pldm_fw_up_pkg.close()
+ except BaseException:
+ pldm_fw_up_pkg.close()
+ os.remove(args.pldmfwuppkgname)
+ raise
+
+
+if __name__ == "__main__":
+ main()