ST / Mbed OS example-IDW01M1-mbed-Cloud-connect
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers combine_bootloader_with_app.py Source File

combine_bootloader_with_app.py

00001 #!/usr/bin/env python
00002 
00003 ## ----------------------------------------------------------------------------
00004 ## Copyright 2016-2017 ARM Ltd.
00005 ##
00006 ## SPDX-License-Identifier: Apache-2.0
00007 ##
00008 ## Licensed under the Apache License, Version 2.0 (the "License");
00009 ## you may not use this file except in compliance with the License.
00010 ## You may obtain a copy of the License at
00011 ##
00012 ##     http://www.apache.org/licenses/LICENSE-2.0
00013 ##
00014 ## Unless required by applicable law or agreed to in writing, software
00015 ## distributed under the License is distributed on an "AS IS" BASIS,
00016 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00017 ## See the License for the specific language governing permissions and
00018 ## limitations under the License.
00019 ## ----------------------------------------------------------------------------
00020 
00021 from os import path
00022 import json
00023 import hashlib, zlib, struct
00024 import time
00025 import sys
00026 from intelhex import IntelHex
00027 
00028 '''
00029 define FIRMWARE_HEADER_MAGIC   0x5a51b3d4UL
00030 define FIRMWARE_HEADER_VERSION 2
00031 define ARM_UC_SHA512_SIZE     (512/8)
00032 define ARM_UC_GUID_SIZE       (128/8)
00033 typedef struct _arm_uc_internal_header_t
00034 {
00035     /* Metadata-header specific magic code */
00036     uint32_t headerMagic;
00037 
00038     /* Revision number for metadata header. */
00039     uint32_t headerVersion;
00040 
00041     /* Version number accompanying the firmware. Larger numbers imply more
00042        recent and preferred versions. This is used for determining the
00043        selection order when multiple versions are available. For downloaded
00044        firmware the manifest timestamp is used as the firmware version.
00045     */
00046     uint64_t firmwareVersion;
00047 
00048     /* Total space (in bytes) occupied by the firmware BLOB. */
00049     uint64_t firmwareSize;
00050 
00051     /* Firmware hash calculated over the firmware size. Should match the hash
00052        generated by standard command line tools, e.g., shasum on Linux/Mac.
00053     */
00054     uint8_t firmwareHash[ARM_UC_SHA512_SIZE];
00055 
00056     /* The ID for the update campaign that resulted in the firmware update.
00057     */
00058     uint8_t campaign[ARM_UC_GUID_SIZE];
00059 
00060     /* Size of the firmware signature. Must be 0 if no signature is supplied. */
00061     uint32_t firmwareSignatureSize;
00062 
00063     /* Header 32 bit CRC. Calculated over the entire header, including the CRC
00064        field, but with the CRC set to zero.
00065     */
00066     uint32_t headerCRC;
00067 
00068     /* Optional firmware signature. Hashing algorithm should be the same as the
00069        one used for the firmware hash. The firmwareSignatureSize must be set.
00070     */
00071     uint8_t firmwareSignature[0];
00072 } arm_uc_internal_header_t;
00073 '''
00074 
00075 # define defaults to go into the metadata header
00076 SIZEOF_SHA512     = int(512/8)
00077 SIZEOF_GUID       = int(128/8)
00078 FIRMWARE_HEADER_MAGIC = 0x5a51b3d4
00079 FIRMWARE_HEADER_VERSION = 2
00080 header_format = ">2I2Q{}s{}s2I".format(SIZEOF_SHA512, SIZEOF_GUID)
00081 
00082 if sys.version_info < (3,):
00083     def b(x):
00084         return bytearray(x)
00085 else:
00086     def b(x):
00087         return x
00088 
00089 def create_header(app_blob, firmwareVersion):
00090     # calculate the hash of the application
00091     firmwareHash = hashlib.sha256(app_blob).digest()
00092 
00093     # calculate the total size which is defined as the application size + metadata header
00094     firmwareSize = len(app_blob)
00095 
00096     # set campaign GUID to 0
00097     campaign = b'\00'
00098 
00099     # signature not supported, set size to 0
00100     signatureSize = 0
00101 
00102     print ('imageSize:    {}'.format(firmwareSize))
00103     print ('imageHash:    {}'.format(''.join(['{:0>2x}'.format(c) for c in b(firmwareHash)])))
00104     print ('imageversion: {}'.format(firmwareVersion))
00105 
00106     # construct struct for CRC calculation
00107     headerCRC = 0
00108     FirmwareHeader = struct.pack(header_format,
00109                                  FIRMWARE_HEADER_MAGIC,
00110                                  FIRMWARE_HEADER_VERSION,
00111                                  firmwareVersion,
00112                                  firmwareSize,
00113                                  firmwareHash,
00114                                  campaign,
00115                                  signatureSize,
00116                                  headerCRC)
00117 
00118     # calculate checksum over header, including signatureSize but without headerCRC
00119     headerCRC = zlib.crc32(FirmwareHeader[:-4]) & 0xffffffff
00120 
00121     # Pack the data into a binary blob
00122     FirmwareHeader = struct.pack(header_format,
00123                                  FIRMWARE_HEADER_MAGIC,
00124                                  FIRMWARE_HEADER_VERSION,
00125                                  firmwareVersion,
00126                                  firmwareSize,
00127                                  firmwareHash,
00128                                  campaign,
00129                                  signatureSize,
00130                                  headerCRC)
00131 
00132     return FirmwareHeader
00133 
00134 
00135 def combine(bootloader_fn, app_fn, app_addr, hdr_addr, bootloader_addr, output_fn, version, no_bootloader):
00136     ih = IntelHex()
00137 
00138     bootloader_format = bootloader_fn.split('.')[-1]
00139 
00140     # write the bootloader
00141     if not no_bootloader:
00142         print("Using bootloader %s" % bootloader_fn)
00143         if bootloader_format == 'hex':
00144             print("Loading bootloader from hex file.")
00145             ih.fromfile(bootloader_fn, format=bootloader_format)
00146         elif bootloader_format == 'bin':
00147             print("Loading bootloader to address 0x%08x." % bootloader_addr)
00148             ih.loadbin(bootloader_fn, offset=bootloader_addr)
00149         else:
00150             print('Bootloader format can only be .bin or .hex')
00151             exit(-1)
00152 
00153     # write firmware header
00154     app_format=app_fn.split('.')[-1]
00155     if app_format == 'bin':
00156         with open(app_fn, 'rb') as fd:
00157             app_blob = fd.read()
00158     elif app_format == 'hex':
00159         application = IntelHex(app_fn)
00160         app_blob = application.tobinstr()
00161     FirmwareHeader = create_header(app_blob, version)
00162     print("Writing header to address 0x%08x." % hdr_addr)
00163     ih.puts(hdr_addr, FirmwareHeader)
00164 
00165     # write the application
00166     if app_format == 'bin':
00167         print("Loading application to address 0x%08x." % app_addr)
00168         ih.loadbin(app_fn, offset=app_addr)
00169     elif app_format == 'hex':
00170         print("Loading application from hex file")
00171         ih.fromfile(app_fn, format=app_format)
00172 
00173     # output to file
00174     ih.tofile(output_fn, format=output_fn.split('.')[-1])
00175 
00176 
00177 if __name__ == '__main__':
00178     from glob import glob
00179     import argparse
00180 
00181     parser = argparse.ArgumentParser(
00182         description='Combine bootloader with application adding metadata header.')
00183 
00184     def addr_arg(s):
00185         if not isinstance(s, int):
00186             s = eval(s)
00187 
00188         return s
00189 
00190     bin_map = {
00191         'k64f': {
00192             'mem_start': '0x0'
00193         },
00194         'ublox_evk_odin_w2': {
00195             'mem_start': '0x08000000'
00196         },
00197         'nucleo_f429zi': {
00198             'mem_start': '0x08000000'
00199         }
00200     }
00201 
00202     curdir = path.dirname(path.abspath(__file__))
00203 
00204     def parse_mbed_app_addr(mcu, key):
00205         mem_start = bin_map[mcu]["mem_start"]
00206         with open(path.join(curdir, "..", "mbed_app.json")) as fd:
00207             mbed_json = json.load(fd)
00208             addr = mbed_json["target_overrides"][mcu.upper()][key]
00209             return addr_arg(addr)
00210 
00211     # specify arguments
00212     parser.add_argument('-m', '--mcu', type=lambda s : s.lower().replace("-","_"), required=False,
00213                         help='mcu', choices=bin_map.keys())
00214     parser.add_argument('-b', '--bootloader',    type=argparse.FileType('rb'),     required=False,
00215                         help='path to the bootloader binary')
00216     parser.add_argument('-a', '--app',           type=argparse.FileType('rb'),     required=True,
00217                         help='path to application binary')
00218     parser.add_argument('-c', '--app-addr',      type=addr_arg,                    required=False,
00219                         help='address of the application')
00220     parser.add_argument('-d', '--header-addr',   type=addr_arg,                    required=False,
00221                         help='address of the firmware metadata header')
00222     parser.add_argument('-o', '--output',        type=argparse.FileType('wb'),     required=True,
00223                         help='output combined file path')
00224     parser.add_argument('-s', '--set-version',   type=int,                         required=False,
00225                         help='set version number', default=int(time.time()))
00226     parser.add_argument('-nb', '--no-bootloader',action='store_true',              required=False,
00227                         help='Produce output without bootloader. The output only '+
00228                              'contains header + app. requires hex output format')
00229 
00230     # workaround for http://bugs.python.org/issue9694
00231     parser._optionals.title = "arguments"
00232 
00233     # get and validate arguments
00234     args = parser.parse_args()
00235 
00236     # validate the output format
00237     f = args.output.name.split('.')[-1]
00238     if f == 'hex':
00239         output_format = 'hex'
00240     elif f == 'bin':
00241         output_format = 'bin'
00242     else:
00243         print('Output format can only be .bin or .hex')
00244         exit(-1)
00245 
00246     # validate no-bootloader option
00247     if args.no_bootloader and output_format == 'bin':
00248         print('--no-bootloader option requires the output format to be .hex')
00249         exit(-1)
00250 
00251     # validate that we can find a bootloader or no_bootloader is specified
00252     bootloader = None
00253     if not args.no_bootloader:
00254         if args.mcu and not args.bootloader:
00255             bl_list = glob("tools/mbed-bootloader-{}-*".format(args.mcu))
00256             if len(bl_list) == 0:
00257                 print("Specified MCU does not have a binary in this location " + \
00258                       "Please specify bootloader location with -b")
00259                 exit(-1)
00260             elif len(bl_list) > 1:
00261                 print("Specified MCU have more than one binary in this location " + \
00262                       "Please specify bootloader location with -b")
00263                 print(bl_list)
00264                 exit(-1)
00265             else:
00266                 fname = bl_list[0]
00267                 bootloader = open(fname, 'rb')
00268         elif args.bootloader:
00269             bootloader = args.bootloader
00270         elif not (args.mcu or args.bootloader):
00271             print("Please specify bootloader location -b or MCU -m")
00272             exit(-1)
00273 
00274     # get the path of bootloader, application and output
00275     if bootloader:
00276         bootloader_fn = path.abspath(bootloader.name)
00277         bootloader.close()
00278     else:
00279         bootloader_fn = ''
00280 
00281     if bootloader_fn.split('.')[-1] != 'hex' and not args.mcu:
00282         print("Please provide a bootloader in hex format or specify MCU -m")
00283         exit(-1)
00284 
00285     app_fn = path.abspath(args.app.name)
00286     args.app.close()
00287     output_fn = path.abspath(args.output.name)
00288     args.output.close()
00289 
00290     # Use specified addresses or default if none are provided
00291     app_format = app_fn.split('.')[-1]
00292     if(not (args.mcu or args.app_addr or app_format == 'hex')):
00293         print("Please specify app address or MCU")
00294         exit(-1)
00295     if app_format != 'hex':
00296         app_addr = args.app_addr or parse_mbed_app_addr(args.mcu, "target.mbed_app_start")
00297     else:
00298         app_addr = None
00299 
00300     if args.mcu:
00301         mem_start = addr_arg(bin_map[args.mcu]["mem_start"])
00302     else:
00303         mem_start = 0
00304 
00305     if(not (args.mcu or args.header_addr)):
00306         print("Please specify header address or MCU")
00307         exit(-1)
00308     header_addr = args.header_addr or parse_mbed_app_addr(args.mcu, "update-client.application-details")
00309 
00310     # combine application and bootloader adding metadata info
00311     combine(bootloader_fn, app_fn, app_addr, header_addr, mem_start,
00312             output_fn, args.set_version, args.no_bootloader)
00313 
00314     # print the output file path
00315     print('Combined binary:' + output_fn)