blob: 5d603b8c8483cc4214a5ebbddcc19aca4df0c2b7 [file] [log] [blame]
Deepak Kodihalli4ac93442021-04-20 14:44:48 +05301#!/usr/bin/env python3
2
3"""Script to create PLDM FW update package"""
4
5import argparse
6import binascii
7from datetime import datetime
8import json
9import os
10import struct
11import sys
12
13import math
14from bitarray import bitarray
15from bitarray.util import ba2int
16
17string_types = dict([
18 ("Unknown", 0),
19 ("ASCII", 1),
20 ("UTF8", 2),
21 ("UTF16", 3),
22 ("UTF16LE", 4),
23 ("UTF16BE", 5)])
24
Tom Josephda2aaab2021-08-01 19:23:09 -070025initial_descriptor_type_name_length = {
Deepak Kodihalli4ac93442021-04-20 14:44:48 +053026 0x0000: ["PCI Vendor ID", 2],
27 0x0001: ["IANA Enterprise ID", 4],
28 0x0002: ["UUID", 16],
29 0x0003: ["PnP Vendor ID", 3],
30 0x0004: ["ACPI Vendor ID", 4]}
31
Tom Josephda2aaab2021-08-01 19:23:09 -070032descriptor_type_name_length = {
33 0x0000: ["PCI Vendor ID", 2],
34 0x0001: ["IANA Enterprise ID", 4],
35 0x0002: ["UUID", 16],
36 0x0003: ["PnP Vendor ID", 3],
37 0x0004: ["ACPI Vendor ID", 4],
38 0x0100: ["PCI Device ID", 2],
39 0x0101: ["PCI Subsystem Vendor ID", 2],
40 0x0102: ["PCI Subsystem ID", 2],
41 0x0103: ["PCI Revision ID", 1],
42 0x0104: ["PnP Product Identifier", 4],
43 0x0105: ["ACPI Product Identifier", 4]}
44
Deepak Kodihalli4ac93442021-04-20 14:44:48 +053045
46def check_string_length(string):
47 """Check if the length of the string is not greater than 255."""
48 if len(string) > 255:
49 sys.exit("ERROR: Max permitted string length is 255")
50
51
52def write_pkg_release_date_time(pldm_fw_up_pkg, release_date_time):
53 '''
54 Write the timestamp into the package header. The timestamp is formatted as
55 series of 13 bytes defined in DSP0240 specification.
56
57 Parameters:
58 pldm_fw_up_pkg: PLDM FW update package
59 release_date_time: Package Release Date Time
60 '''
61 time = release_date_time.time()
62 date = release_date_time.date()
63 us_bytes = time.microsecond.to_bytes(3, byteorder='little')
64 pldm_fw_up_pkg.write(
65 struct.pack(
66 '<hBBBBBBBBHB',
67 0,
68 us_bytes[0],
69 us_bytes[1],
70 us_bytes[2],
71 time.second,
72 time.minute,
73 time.hour,
74 date.day,
75 date.month,
76 date.year,
77 0))
78
79
80def write_package_version_string(pldm_fw_up_pkg, metadata):
81 '''
82 Write PackageVersionStringType, PackageVersionStringLength and
83 PackageVersionString to the package header.
84
85 Parameters:
86 pldm_fw_up_pkg: PLDM FW update package
87 metadata: metadata about PLDM FW update package
88 '''
89 # Hardcoded string type to ASCII
90 string_type = string_types["ASCII"]
91 package_version_string = \
92 metadata["PackageHeaderInformation"]["PackageVersionString"]
93 check_string_length(package_version_string)
94 format_string = '<BB' + str(len(package_version_string)) + 's'
95 pldm_fw_up_pkg.write(
96 struct.pack(
97 format_string,
98 string_type,
99 len(package_version_string),
100 package_version_string.encode('ascii')))
101
102
103def write_component_bitmap_bit_length(pldm_fw_up_pkg, metadata):
104 '''
105 ComponentBitmapBitLength in the package header indicates the number of bits
106 that will be used represent the bitmap in the ApplicableComponents field
107 for a matching device. The value shall be a multiple of 8 and be large
108 enough to contain a bit for each component in the package. The number of
109 components in the JSON file is used to populate the bitmap length.
110
111 Parameters:
112 pldm_fw_up_pkg: PLDM FW update package
113 metadata: metadata about PLDM FW update package
114
115 Returns:
116 ComponentBitmapBitLength: number of bits that will be used
117 represent the bitmap in the ApplicableComponents field for a
118 matching device
119 '''
120 # The script supports upto 32 components now
121 max_components = 32
122 bitmap_multiple = 8
123
124 num_components = len(metadata["ComponentImageInformationArea"])
125 if num_components > max_components:
126 sys.exit("ERROR: only upto 32 components supported now")
127 component_bitmap_bit_length = bitmap_multiple * \
128 math.ceil(num_components/bitmap_multiple)
129 pldm_fw_up_pkg.write(struct.pack('<H', int(component_bitmap_bit_length)))
130 return component_bitmap_bit_length
131
132
133def write_pkg_header_info(pldm_fw_up_pkg, metadata):
134 '''
135 ComponentBitmapBitLength in the package header indicates the number of bits
136 that will be used represent the bitmap in the ApplicableComponents field
137 for a matching device. The value shall be a multiple of 8 and be large
138 enough to contain a bit for each component in the package. The number of
139 components in the JSON file is used to populate the bitmap length.
140
141 Parameters:
142 pldm_fw_up_pkg: PLDM FW update package
143 metadata: metadata about PLDM FW update package
144
145 Returns:
146 ComponentBitmapBitLength: number of bits that will be used
147 represent the bitmap in the ApplicableComponents field for a
148 matching device
149 '''
150 uuid = metadata["PackageHeaderInformation"]["PackageHeaderIdentifier"]
151 package_header_identifier = bytearray.fromhex(uuid)
152 pldm_fw_up_pkg.write(package_header_identifier)
153
154 package_header_format_revision = \
155 metadata["PackageHeaderInformation"]["PackageHeaderFormatVersion"]
156 # Size will be computed and updated subsequently
157 package_header_size = 0
158 pldm_fw_up_pkg.write(
159 struct.pack(
160 '<BH',
161 package_header_format_revision,
162 package_header_size))
163
164 try:
165 release_date_time = datetime.strptime(
166 metadata["PackageHeaderInformation"]["PackageReleaseDateTime"],
167 "%d/%m/%Y %H:%M:%S")
168 write_pkg_release_date_time(pldm_fw_up_pkg, release_date_time)
169 except KeyError:
170 write_pkg_release_date_time(pldm_fw_up_pkg, datetime.now())
171
172 component_bitmap_bit_length = write_component_bitmap_bit_length(
173 pldm_fw_up_pkg, metadata)
174 write_package_version_string(pldm_fw_up_pkg, metadata)
175 return component_bitmap_bit_length
176
177
178def get_applicable_components(device, components, component_bitmap_bit_length):
179 '''
180 This function figures out the components applicable for the device and sets
181 the ApplicableComponents bitfield accordingly.
182
183 Parameters:
184 device: device information
185 components: list of components in the package
186 component_bitmap_bit_length: length of the ComponentBitmapBitLength
187
188 Returns:
189 The ApplicableComponents bitfield
190 '''
191 applicable_components_list = device["ApplicableComponents"]
192 applicable_components = bitarray(component_bitmap_bit_length,
193 endian='little')
194 applicable_components.setall(0)
195 for component in components:
196 if component["ComponentIdentifier"] in applicable_components_list:
197 applicable_components[components.index(component)] = 1
198 return applicable_components
199
200
Tom Josephda2aaab2021-08-01 19:23:09 -0700201def prepare_record_descriptors(descriptors):
202 '''
203 This function processes the Descriptors and prepares the RecordDescriptors
204 section of the the firmware device ID record.
205
206 Parameters:
207 descriptors: Descriptors entry
208
209 Returns:
210 RecordDescriptors, DescriptorCount
211 '''
212 record_descriptors = bytearray()
213 vendor_defined_desc_type = 65535
214 vendor_desc_title_str_type_len = 1
215 vendor_desc_title_str_len_len = 1
216 descriptor_count = 0
217
218 for descriptor in descriptors:
219
220 descriptor_type = descriptor["DescriptorType"]
221 if descriptor_count == 0:
222 if initial_descriptor_type_name_length.get(descriptor_type) \
223 is None:
224 sys.exit("ERROR: Initial descriptor type not supported")
225 else:
226 if descriptor_type_name_length.get(descriptor_type) is None and \
227 descriptor_type != vendor_defined_desc_type:
228 sys.exit("ERROR: Descriptor type not supported")
229
230 if descriptor_type == vendor_defined_desc_type:
231 vendor_desc_title_str = \
232 descriptor["VendorDefinedDescriptorTitleString"]
233 vendor_desc_data = descriptor["VendorDefinedDescriptorData"]
234 check_string_length(vendor_desc_title_str)
235 vendor_desc_title_str_type = string_types["ASCII"]
236 descriptor_length = vendor_desc_title_str_type_len + \
237 vendor_desc_title_str_len_len + len(vendor_desc_title_str) + \
238 len(bytearray.fromhex(vendor_desc_data))
239 format_string = '<HHBB' + str(len(vendor_desc_title_str)) + 's'
240 record_descriptors.extend(struct.pack(
241 format_string,
242 descriptor_type,
243 descriptor_length,
244 vendor_desc_title_str_type,
245 len(vendor_desc_title_str),
246 vendor_desc_title_str.encode('ascii')))
247 record_descriptors.extend(bytearray.fromhex(vendor_desc_data))
248 descriptor_count += 1
249 else:
250 descriptor_type = descriptor["DescriptorType"]
251 descriptor_data = descriptor["DescriptorData"]
252 descriptor_length = len(bytearray.fromhex(descriptor_data))
253 if descriptor_length != \
254 descriptor_type_name_length.get(descriptor_type)[1]:
255 err_string = "ERROR: Descriptor type - " + \
256 descriptor_type_name_length.get(descriptor_type)[0] + \
257 " length is incorrect"
258 sys.exit(err_string)
259 format_string = '<HH'
260 record_descriptors.extend(struct.pack(
261 format_string,
262 descriptor_type,
263 descriptor_length))
264 record_descriptors.extend(bytearray.fromhex(descriptor_data))
265 descriptor_count += 1
266 return record_descriptors, descriptor_count
267
268
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530269def write_fw_device_identification_area(pldm_fw_up_pkg, metadata,
270 component_bitmap_bit_length):
271 '''
272 Write firmware device ID records into the PLDM package header
273
274 This function writes the DeviceIDRecordCount and the
275 FirmwareDeviceIDRecords into the firmware update package by processing the
276 metadata JSON. Currently there is no support for optional
Tom Josephda2aaab2021-08-01 19:23:09 -0700277 FirmwareDevicePackageData.
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530278
279 Parameters:
280 pldm_fw_up_pkg: PLDM FW update package
281 metadata: metadata about PLDM FW update package
282 component_bitmap_bit_length: length of the ComponentBitmapBitLength
283 '''
284 # The spec limits the number of firmware device ID records to 255
285 max_device_id_record_count = 255
286 devices = metadata["FirmwareDeviceIdentificationArea"]
287 device_id_record_count = len(devices)
288 if device_id_record_count > max_device_id_record_count:
289 sys.exit(
290 "ERROR: there can be only upto 255 entries in the \
291 FirmwareDeviceIdentificationArea section")
292
293 # DeviceIDRecordCount
294 pldm_fw_up_pkg.write(struct.pack('<B', device_id_record_count))
295
296 for device in devices:
297 # RecordLength size
298 record_length = 2
299
Tom Josephda2aaab2021-08-01 19:23:09 -0700300 # DescriptorCount
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530301 record_length += 1
302
303 # DeviceUpdateOptionFlags
304 device_update_option_flags = bitarray(32, endian='little')
305 device_update_option_flags.setall(0)
306 # Continue component updates after failure
307 supported_device_update_option_flags = [0]
308 for option in device["DeviceUpdateOptionFlags"]:
309 if option not in supported_device_update_option_flags:
310 sys.exit("ERROR: unsupported DeviceUpdateOptionFlag entry")
311 device_update_option_flags[option] = 1
312 record_length += 4
313
314 # ComponentImageSetVersionStringType supports only ASCII for now
315 component_image_set_version_string_type = string_types["ASCII"]
316 record_length += 1
317
318 # ComponentImageSetVersionStringLength
319 component_image_set_version_string = \
320 device["ComponentImageSetVersionString"]
321 check_string_length(component_image_set_version_string)
322 record_length += len(component_image_set_version_string)
323 record_length += 1
324
325 # Optional FirmwareDevicePackageData not supported now,
326 # FirmwareDevicePackageDataLength is set to 0x0000
327 fw_device_pkg_data_length = 0
328 record_length += 2
329
330 # ApplicableComponents
331 components = metadata["ComponentImageInformationArea"]
332 applicable_components = \
333 get_applicable_components(device,
334 components,
335 component_bitmap_bit_length)
336 applicable_components_bitfield_length = \
337 round(len(applicable_components)/8)
338 record_length += applicable_components_bitfield_length
339
Tom Josephda2aaab2021-08-01 19:23:09 -0700340 # RecordDescriptors
341 descriptors = device["Descriptors"]
342 record_descriptors, descriptor_count = \
343 prepare_record_descriptors(descriptors)
344 record_length += len(record_descriptors)
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530345
346 format_string = '<HBIBBH' + \
347 str(applicable_components_bitfield_length) + 's' + \
Tom Josephda2aaab2021-08-01 19:23:09 -0700348 str(len(component_image_set_version_string)) + 's'
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530349 pldm_fw_up_pkg.write(
350 struct.pack(
351 format_string,
352 record_length,
353 descriptor_count,
354 ba2int(device_update_option_flags),
355 component_image_set_version_string_type,
356 len(component_image_set_version_string),
357 fw_device_pkg_data_length,
358 applicable_components.tobytes(),
Tom Josephda2aaab2021-08-01 19:23:09 -0700359 component_image_set_version_string.encode('ascii')))
360 pldm_fw_up_pkg.write(record_descriptors)
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530361
362
363def write_component_image_info_area(pldm_fw_up_pkg, metadata, image_files):
364 '''
365 Write component image information area into the PLDM package header
366
367 This function writes the ComponentImageCount and the
368 ComponentImageInformation into the firmware update package by processing
369 the metadata JSON. Currently there is no support for
370 ComponentComparisonStamp field and the component option use component
371 comparison stamp.
372
373 Parameters:
374 pldm_fw_up_pkg: PLDM FW update package
375 metadata: metadata about PLDM FW update package
376 image_files: component images
377 '''
378 components = metadata["ComponentImageInformationArea"]
379 # ComponentImageCount
380 pldm_fw_up_pkg.write(struct.pack('<H', len(components)))
381 component_location_offsets = []
382 # ComponentLocationOffset position in individual component image
383 # information
384 component_location_offset_pos = 12
385
386 for component in components:
387 # Record the location of the ComponentLocationOffset to be updated
388 # after appending images to the firmware update package
389 component_location_offsets.append(pldm_fw_up_pkg.tell() +
390 component_location_offset_pos)
391
392 # ComponentClassification
393 component_classification = component["ComponentClassification"]
394 if component_classification < 0 or component_classification > 0xFFFF:
395 sys.exit(
396 "ERROR: ComponentClassification should be [0x0000 - 0xFFFF]")
397
398 # ComponentIdentifier
399 component_identifier = component["ComponentIdentifier"]
400 if component_identifier < 0 or component_identifier > 0xFFFF:
401 sys.exit(
402 "ERROR: ComponentIdentifier should be [0x0000 - 0xFFFF]")
403
404 # ComponentComparisonStamp not supported
405 component_comparison_stamp = 0xFFFFFFFF
406
407 # ComponentOptions
408 component_options = bitarray(16, endian='little')
409 component_options.setall(0)
410 supported_component_options = [0]
411 for option in component["ComponentOptions"]:
412 if option not in supported_component_options:
413 sys.exit(
414 "ERROR: unsupported ComponentOption in\
415 ComponentImageInformationArea section")
416 component_options[option] = 1
417
418 # RequestedComponentActivationMethod
419 requested_component_activation_method = bitarray(16, endian='little')
420 requested_component_activation_method.setall(0)
421 supported_requested_component_activation_method = [0, 1, 2, 3, 4, 5]
422 for option in component["RequestedComponentActivationMethod"]:
423 if option not in supported_requested_component_activation_method:
424 sys.exit(
425 "ERROR: unsupported RequestedComponent\
426 ActivationMethod entry")
427 requested_component_activation_method[option] = 1
428
429 # ComponentLocationOffset
430 component_location_offset = 0
431 # ComponentSize
432 component_size = 0
433 # ComponentVersionStringType
434 component_version_string_type = string_types["ASCII"]
435 # ComponentVersionStringlength
436 # ComponentVersionString
437 component_version_string = component["ComponentVersionString"]
438 check_string_length(component_version_string)
439
440 format_string = '<HHIHHIIBB' + str(len(component_version_string)) + 's'
441 pldm_fw_up_pkg.write(
442 struct.pack(
443 format_string,
444 component_classification,
445 component_identifier,
446 component_comparison_stamp,
447 ba2int(component_options),
448 ba2int(requested_component_activation_method),
449 component_location_offset,
450 component_size,
451 component_version_string_type,
452 len(component_version_string),
453 component_version_string.encode('ascii')))
454
455 index = 0
456 pkg_header_checksum_size = 4
457 start_offset = pldm_fw_up_pkg.tell() + pkg_header_checksum_size
458 # Update ComponentLocationOffset and ComponentSize for all the components
459 for offset in component_location_offsets:
460 file_size = os.stat(image_files[index]).st_size
461 pldm_fw_up_pkg.seek(offset)
462 pldm_fw_up_pkg.write(
463 struct.pack(
464 '<II', start_offset, file_size))
465 start_offset += file_size
466 index += 1
467 pldm_fw_up_pkg.seek(0, os.SEEK_END)
468
469
470def write_pkg_header_checksum(pldm_fw_up_pkg):
471 '''
472 Write PackageHeaderChecksum into the PLDM package header.
473
474 Parameters:
475 pldm_fw_up_pkg: PLDM FW update package
476 '''
477 pldm_fw_up_pkg.seek(0)
478 package_header_checksum = binascii.crc32(pldm_fw_up_pkg.read())
479 pldm_fw_up_pkg.seek(0, os.SEEK_END)
480 pldm_fw_up_pkg.write(struct.pack('<I', package_header_checksum))
481
482
483def update_pkg_header_size(pldm_fw_up_pkg):
484 '''
485 Update PackageHeader in the PLDM package header. The package header size
486 which is the count of all bytes in the PLDM package header structure is
487 calculated once the package header contents is complete.
488
489 Parameters:
490 pldm_fw_up_pkg: PLDM FW update package
491 '''
Tom Joseph33ac59d2021-07-30 11:09:54 -0700492 pkg_header_checksum_size = 4
493 file_size = pldm_fw_up_pkg.tell() + pkg_header_checksum_size
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530494 pkg_header_size_offset = 17
495 # Seek past PackageHeaderIdentifier and PackageHeaderFormatRevision
496 pldm_fw_up_pkg.seek(pkg_header_size_offset)
497 pldm_fw_up_pkg.write(struct.pack('<H', file_size))
498 pldm_fw_up_pkg.seek(0, os.SEEK_END)
499
500
501def append_component_images(pldm_fw_up_pkg, image_files):
502 '''
503 Append the component images to the firmware update package.
504
505 Parameters:
506 pldm_fw_up_pkg: PLDM FW update package
507 image_files: component images
508 '''
509 for image in image_files:
510 with open(image, 'rb') as file:
511 for line in file:
512 pldm_fw_up_pkg.write(line)
513
514
515def main():
516 """Create PLDM FW update (DSP0267) package based on a JSON metadata file"""
517 parser = argparse.ArgumentParser()
518 parser.add_argument("pldmfwuppkgname",
519 help="Name of the PLDM FW update package")
520 parser.add_argument("metadatafile", help="Path of metadata JSON file")
521 parser.add_argument(
522 "images", nargs='+',
523 help="One or more firmware image paths, in the same order as\
524 ComponentImageInformationArea entries")
525
526 args = parser.parse_args()
527 image_files = args.images
528 with open(args.metadatafile) as file:
529 try:
530 metadata = json.load(file)
531 except ValueError:
532 sys.exit("ERROR: Invalid metadata JSON file")
533
534 # Validate the number of component images
535 if len(image_files) != len(metadata["ComponentImageInformationArea"]):
536 sys.exit("ERROR: number of images passed != number of entries \
537 in ComponentImageInformationArea")
538
539 try:
540 with open(args.pldmfwuppkgname, 'w+b') as pldm_fw_up_pkg:
541 component_bitmap_bit_length = write_pkg_header_info(pldm_fw_up_pkg,
542 metadata)
543 write_fw_device_identification_area(pldm_fw_up_pkg,
544 metadata,
545 component_bitmap_bit_length)
546 write_component_image_info_area(pldm_fw_up_pkg, metadata,
547 image_files)
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530548 update_pkg_header_size(pldm_fw_up_pkg)
Tom Joseph33ac59d2021-07-30 11:09:54 -0700549 write_pkg_header_checksum(pldm_fw_up_pkg)
Deepak Kodihalli4ac93442021-04-20 14:44:48 +0530550 append_component_images(pldm_fw_up_pkg, image_files)
551 pldm_fw_up_pkg.close()
552 except BaseException:
553 pldm_fw_up_pkg.close()
554 os.remove(args.pldmfwuppkgname)
555 raise
556
557
558if __name__ == "__main__":
559 main()