Mbed Cloud Connect for Ethernet Platforms
Fork of example-Ethernet-mbed-Cloud-connect by
Diff: tools/combine_bootloader_with_app.py
- Branch:
- update-1.3.0
- Revision:
- 6:2d0e0866f2e7
diff -r 53eff875f5d0 -r 2d0e0866f2e7 tools/combine_bootloader_with_app.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/combine_bootloader_with_app.py Fri May 11 16:15:18 2018 +0100 @@ -0,0 +1,315 @@ +#!/usr/bin/env python + +## ---------------------------------------------------------------------------- +## Copyright 2016-2017 ARM Ltd. +## +## SPDX-License-Identifier: Apache-2.0 +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## ---------------------------------------------------------------------------- + +from os import path +import json +import hashlib, zlib, struct +import time +import sys +from intelhex import IntelHex + +''' +define FIRMWARE_HEADER_MAGIC 0x5a51b3d4UL +define FIRMWARE_HEADER_VERSION 2 +define ARM_UC_SHA512_SIZE (512/8) +define ARM_UC_GUID_SIZE (128/8) +typedef struct _arm_uc_internal_header_t +{ + /* Metadata-header specific magic code */ + uint32_t headerMagic; + + /* Revision number for metadata header. */ + uint32_t headerVersion; + + /* Version number accompanying the firmware. Larger numbers imply more + recent and preferred versions. This is used for determining the + selection order when multiple versions are available. For downloaded + firmware the manifest timestamp is used as the firmware version. + */ + uint64_t firmwareVersion; + + /* Total space (in bytes) occupied by the firmware BLOB. */ + uint64_t firmwareSize; + + /* Firmware hash calculated over the firmware size. Should match the hash + generated by standard command line tools, e.g., shasum on Linux/Mac. + */ + uint8_t firmwareHash[ARM_UC_SHA512_SIZE]; + + /* The ID for the update campaign that resulted in the firmware update. + */ + uint8_t campaign[ARM_UC_GUID_SIZE]; + + /* Size of the firmware signature. Must be 0 if no signature is supplied. */ + uint32_t firmwareSignatureSize; + + /* Header 32 bit CRC. Calculated over the entire header, including the CRC + field, but with the CRC set to zero. + */ + uint32_t headerCRC; + + /* Optional firmware signature. Hashing algorithm should be the same as the + one used for the firmware hash. The firmwareSignatureSize must be set. + */ + uint8_t firmwareSignature[0]; +} arm_uc_internal_header_t; +''' + +# define defaults to go into the metadata header +SIZEOF_SHA512 = int(512/8) +SIZEOF_GUID = int(128/8) +FIRMWARE_HEADER_MAGIC = 0x5a51b3d4 +FIRMWARE_HEADER_VERSION = 2 +header_format = ">2I2Q{}s{}s2I".format(SIZEOF_SHA512, SIZEOF_GUID) + +if sys.version_info < (3,): + def b(x): + return bytearray(x) +else: + def b(x): + return x + +def create_header(app_blob, firmwareVersion): + # calculate the hash of the application + firmwareHash = hashlib.sha256(app_blob).digest() + + # calculate the total size which is defined as the application size + metadata header + firmwareSize = len(app_blob) + + # set campaign GUID to 0 + campaign = b'\00' + + # signature not supported, set size to 0 + signatureSize = 0 + + print ('imageSize: {}'.format(firmwareSize)) + print ('imageHash: {}'.format(''.join(['{:0>2x}'.format(c) for c in b(firmwareHash)]))) + print ('imageversion: {}'.format(firmwareVersion)) + + # construct struct for CRC calculation + headerCRC = 0 + FirmwareHeader = struct.pack(header_format, + FIRMWARE_HEADER_MAGIC, + FIRMWARE_HEADER_VERSION, + firmwareVersion, + firmwareSize, + firmwareHash, + campaign, + signatureSize, + headerCRC) + + # calculate checksum over header, including signatureSize but without headerCRC + headerCRC = zlib.crc32(FirmwareHeader[:-4]) & 0xffffffff + + # Pack the data into a binary blob + FirmwareHeader = struct.pack(header_format, + FIRMWARE_HEADER_MAGIC, + FIRMWARE_HEADER_VERSION, + firmwareVersion, + firmwareSize, + firmwareHash, + campaign, + signatureSize, + headerCRC) + + return FirmwareHeader + + +def combine(bootloader_fn, app_fn, app_addr, hdr_addr, bootloader_addr, output_fn, version, no_bootloader): + ih = IntelHex() + + bootloader_format = bootloader_fn.split('.')[-1] + + # write the bootloader + if not no_bootloader: + print("Using bootloader %s" % bootloader_fn) + if bootloader_format == 'hex': + print("Loading bootloader from hex file.") + ih.fromfile(bootloader_fn, format=bootloader_format) + elif bootloader_format == 'bin': + print("Loading bootloader to address 0x%08x." % bootloader_addr) + ih.loadbin(bootloader_fn, offset=bootloader_addr) + else: + print('Bootloader format can only be .bin or .hex') + exit(-1) + + # write firmware header + app_format=app_fn.split('.')[-1] + if app_format == 'bin': + with open(app_fn, 'rb') as fd: + app_blob = fd.read() + elif app_format == 'hex': + application = IntelHex(app_fn) + app_blob = application.tobinstr() + FirmwareHeader = create_header(app_blob, version) + print("Writing header to address 0x%08x." % hdr_addr) + ih.puts(hdr_addr, FirmwareHeader) + + # write the application + if app_format == 'bin': + print("Loading application to address 0x%08x." % app_addr) + ih.loadbin(app_fn, offset=app_addr) + elif app_format == 'hex': + print("Loading application from hex file") + ih.fromfile(app_fn, format=app_format) + + # output to file + ih.tofile(output_fn, format=output_fn.split('.')[-1]) + + +if __name__ == '__main__': + from glob import glob + import argparse + + parser = argparse.ArgumentParser( + description='Combine bootloader with application adding metadata header.') + + def addr_arg(s): + if not isinstance(s, int): + s = eval(s) + + return s + + bin_map = { + 'k64f': { + 'mem_start': '0x0' + }, + 'ublox_evk_odin_w2': { + 'mem_start': '0x08000000' + }, + 'nucleo_f429zi': { + 'mem_start': '0x08000000' + } + } + + curdir = path.dirname(path.abspath(__file__)) + + def parse_mbed_app_addr(mcu, key): + mem_start = bin_map[mcu]["mem_start"] + with open(path.join(curdir, "..", "mbed_app.json")) as fd: + mbed_json = json.load(fd) + addr = mbed_json["target_overrides"][mcu.upper()][key] + return addr_arg(addr) + + # specify arguments + parser.add_argument('-m', '--mcu', type=lambda s : s.lower().replace("-","_"), required=False, + help='mcu', choices=bin_map.keys()) + parser.add_argument('-b', '--bootloader', type=argparse.FileType('rb'), required=False, + help='path to the bootloader binary') + parser.add_argument('-a', '--app', type=argparse.FileType('rb'), required=True, + help='path to application binary') + parser.add_argument('-c', '--app-addr', type=addr_arg, required=False, + help='address of the application') + parser.add_argument('-d', '--header-addr', type=addr_arg, required=False, + help='address of the firmware metadata header') + parser.add_argument('-o', '--output', type=argparse.FileType('wb'), required=True, + help='output combined file path') + parser.add_argument('-s', '--set-version', type=int, required=False, + help='set version number', default=int(time.time())) + parser.add_argument('-nb', '--no-bootloader',action='store_true', required=False, + help='Produce output without bootloader. The output only '+ + 'contains header + app. requires hex output format') + + # workaround for http://bugs.python.org/issue9694 + parser._optionals.title = "arguments" + + # get and validate arguments + args = parser.parse_args() + + # validate the output format + f = args.output.name.split('.')[-1] + if f == 'hex': + output_format = 'hex' + elif f == 'bin': + output_format = 'bin' + else: + print('Output format can only be .bin or .hex') + exit(-1) + + # validate no-bootloader option + if args.no_bootloader and output_format == 'bin': + print('--no-bootloader option requires the output format to be .hex') + exit(-1) + + # validate that we can find a bootloader or no_bootloader is specified + bootloader = None + if not args.no_bootloader: + if args.mcu and not args.bootloader: + bl_list = glob("tools/mbed-bootloader-{}-*".format(args.mcu)) + if len(bl_list) == 0: + print("Specified MCU does not have a binary in this location " + \ + "Please specify bootloader location with -b") + exit(-1) + elif len(bl_list) > 1: + print("Specified MCU have more than one binary in this location " + \ + "Please specify bootloader location with -b") + print(bl_list) + exit(-1) + else: + fname = bl_list[0] + bootloader = open(fname, 'rb') + elif args.bootloader: + bootloader = args.bootloader + elif not (args.mcu or args.bootloader): + print("Please specify bootloader location -b or MCU -m") + exit(-1) + + # get the path of bootloader, application and output + if bootloader: + bootloader_fn = path.abspath(bootloader.name) + bootloader.close() + else: + bootloader_fn = '' + + if bootloader_fn.split('.')[-1] != 'hex' and not args.mcu: + print("Please provide a bootloader in hex format or specify MCU -m") + exit(-1) + + app_fn = path.abspath(args.app.name) + args.app.close() + output_fn = path.abspath(args.output.name) + args.output.close() + + # Use specified addresses or default if none are provided + app_format = app_fn.split('.')[-1] + if(not (args.mcu or args.app_addr or app_format == 'hex')): + print("Please specify app address or MCU") + exit(-1) + if app_format != 'hex': + app_addr = args.app_addr or parse_mbed_app_addr(args.mcu, "target.mbed_app_start") + else: + app_addr = None + + if args.mcu: + mem_start = addr_arg(bin_map[args.mcu]["mem_start"]) + else: + mem_start = 0 + + if(not (args.mcu or args.header_addr)): + print("Please specify header address or MCU") + exit(-1) + header_addr = args.header_addr or parse_mbed_app_addr(args.mcu, "update-client.application-details") + + # combine application and bootloader adding metadata info + combine(bootloader_fn, app_fn, app_addr, header_addr, mem_start, + output_fn, args.set_version, args.no_bootloader) + + # print the output file path + print('Combined binary:' + output_fn)