vos/ambiq-hal-sys/ambiq-sparkfun-sdk/boards_sfe/common/tools_sfe/asb/asb.py
2022-10-23 23:45:43 -07:00

900 lines
39 KiB
Python

#!/usr/bin/env python3
# Combination of the three steps to take an 'application.bin' file and run it on a SparkFun Artemis module
# Information:
# This script performs the three main tasks:
# 1. Convert 'application.bin' to an OTA update blob
# 2. Convert the OTA blob into a wired update blob
# 3. Push the wired update blob into the Artemis module
import argparse
import sys
from Crypto.Cipher import AES
import array
import hashlib
import hmac
import os
import binascii
import serial
import serial.tools.list_ports as list_ports
import time
# from sf_am_defines import *
from sys import exit
from am_defines import *
from keys_info import keyTblAes, keyTblHmac, minAesKeyIdx, maxAesKeyIdx, minHmacKeyIdx, maxHmacKeyIdx, INFO_KEY, FLASH_KEY
#******************************************************************************
#
# Global Variables
#
#******************************************************************************
loadTries = 0 #If we fail, try again. Tracks the number of tries we've attempted
loadSuccess = False
blob2wiredfile = ''
uploadbinfile = ''
#******************************************************************************
#
# Generate the image blob as per command line parameters
#
#******************************************************************************
def bin2blob_process(loadaddress, appFile, magicNum, crcI, crcB, authI, authB, protection, authKeyIdx, output, encKeyIdx, version, erasePrev, child0, child1, authalgo, encalgo):
global blob2wiredfile
app_binarray = bytearray()
# Open the file, and read it into an array of integers.
with appFile as f_app:
app_binarray.extend(f_app.read())
f_app.close()
encVal = 0
if (encalgo != 0):
encVal = 1
if ((encKeyIdx < minAesKeyIdx) or (encKeyIdx > maxAesKeyIdx)):
am_print("Invalid encKey Idx ", encKeyIdx, level=AM_PRINT_LEVEL_ERROR);
return
if (encalgo == 2):
if (encKeyIdx & 0x1):
am_print("Invalid encKey Idx ", encKeyIdx, level=AM_PRINT_LEVEL_ERROR);
return
keySize = 32
else:
keySize = 16
if (authalgo != 0):
if ((authKeyIdx < minHmacKeyIdx) or (authKeyIdx > maxHmacKeyIdx) or (authKeyIdx & 0x1)):
am_print("Invalid authKey Idx ", authKeyIdx, level=AM_PRINT_LEVEL_ERROR);
return
if (magicNum == AM_IMAGE_MAGIC_MAIN):
hdr_length = AM_IMAGEHDR_SIZE_MAIN; #fixed header length
elif ((magicNum == AM_IMAGE_MAGIC_CHILD) or (magicNum == AM_IMAGE_MAGIC_CUSTPATCH) or (magicNum == AM_IMAGE_MAGIC_NONSECURE) or (magicNum == AM_IMAGE_MAGIC_INFO0)):
hdr_length = AM_IMAGEHDR_SIZE_AUX; #fixed header length
else:
am_print("magic number", hex(magicNum), " not supported", level=AM_PRINT_LEVEL_ERROR)
return
am_print("Header Size = ", hex(hdr_length))
#generate mutable byte array for the header
hdr_binarray = bytearray([0x00]*hdr_length);
orig_app_length = (len(app_binarray))
am_print("original app_size ",hex(orig_app_length), "(",orig_app_length,")")
am_print("load_address ",hex(loadaddress), "(",loadaddress,")")
if (loadaddress & 0x3):
am_print("load address needs to be word aligned", level=AM_PRINT_LEVEL_ERROR)
return
if (magicNum == AM_IMAGE_MAGIC_INFO0):
if (orig_app_length & 0x3):
am_print("INFO0 blob length needs to be multiple of 4", level=AM_PRINT_LEVEL_ERROR)
return
if ((loadaddress + orig_app_length) > INFO_SIZE_BYTES):
am_print("INFO0 Offset and length exceed size", level=AM_PRINT_LEVEL_ERROR)
return
if (encVal == 1):
block_size = AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES
app_binarray = pad_to_block_size(app_binarray, block_size, 1)
else:
# Add Padding
app_binarray = pad_to_block_size(app_binarray, 4, 0)
app_length = (len(app_binarray))
am_print("app_size ",hex(app_length), "(",app_length,")")
# Create Image blobs
# w0
blobLen = hdr_length + app_length
w0 = (magicNum << 24) | ((encVal & 0x1) << 23) | blobLen
am_print("w0 =", hex(w0))
fill_word(hdr_binarray, 0, w0)
# w2
securityVal = ((authI << 1) | crcI) << 4 | (authB << 1) | crcB
am_print("Security Value ", hex(securityVal))
w2 = ((securityVal << 24) & 0xff000000) | ((authalgo) & 0xf) | ((authKeyIdx << 4) & 0xf0) | ((encalgo << 8) & 0xf00) | ((encKeyIdx << 12) & 0xf000)
fill_word(hdr_binarray, 8, w2)
am_print("w2 = ",hex(w2))
if (magicNum == AM_IMAGE_MAGIC_INFO0):
# Insert the INFO0 size and offset
addrWord = ((orig_app_length>>2) << 16) | ((loadaddress>>2) & 0xFFFF)
versionKeyWord = INFO_KEY
else:
# Insert the application binary load address.
addrWord = loadaddress | (protection & 0x3)
# Initialize versionKeyWord
versionKeyWord = (version & 0x7FFF) | ((erasePrev & 0x1) << 15)
am_print("addrWord = ",hex(addrWord))
fill_word(hdr_binarray, AM_IMAGEHDR_OFFSET_ADDR, addrWord)
am_print("versionKeyWord = ",hex(versionKeyWord))
fill_word(hdr_binarray, AM_IMAGEHDR_OFFSET_VERKEY, versionKeyWord)
# Initialize child (Child Ptr/ Feature key)
am_print("child0/feature = ",hex(child0))
fill_word(hdr_binarray, AM_IMAGEHDR_OFFSET_CHILDPTR, child0)
am_print("child1 = ",hex(child1))
fill_word(hdr_binarray, AM_IMAGEHDR_OFFSET_CHILDPTR + 4, child1)
authKeyIdx = authKeyIdx - minHmacKeyIdx
if (authB != 0): # Authentication needed
am_print("Boot Authentication Enabled")
# am_print("Key used for HMAC")
# am_print([hex(keyTblHmac[authKeyIdx*AM_SECBOOT_KEYIDX_BYTES + n]) for n in range (0, AM_HMAC_SIG_SIZE)])
# Initialize the clear image HMAC
sigClr = compute_hmac(keyTblHmac[authKeyIdx*AM_SECBOOT_KEYIDX_BYTES:(authKeyIdx*AM_SECBOOT_KEYIDX_BYTES+AM_HMAC_SIG_SIZE)], (hdr_binarray[AM_IMAGEHDR_START_HMAC:hdr_length] + app_binarray))
am_print("HMAC Clear")
am_print([hex(n) for n in sigClr])
# Fill up the HMAC
for x in range(0, AM_HMAC_SIG_SIZE):
hdr_binarray[AM_IMAGEHDR_OFFSET_SIGCLR + x] = sigClr[x]
# All the header fields part of the encryption are now final
if (encVal == 1):
am_print("Encryption Enabled")
encKeyIdx = encKeyIdx - minAesKeyIdx
ivValAes = os.urandom(AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES)
am_print("Initialization Vector")
am_print([hex(ivValAes[n]) for n in range (0, AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES)])
keyAes = os.urandom(keySize)
am_print("AES Key used for encryption")
am_print([hex(keyAes[n]) for n in range (0, keySize)])
# Encrypted Part
am_print("Encrypting blob of size " , (hdr_length - AM_IMAGEHDR_START_ENCRYPT + app_length))
enc_binarray = encrypt_app_aes((hdr_binarray[AM_IMAGEHDR_START_ENCRYPT:hdr_length] + app_binarray), keyAes, ivValAes)
# am_print("Key used for encrypting AES Key")
# am_print([hex(keyTblAes[encKeyIdx*keySize + n]) for n in range (0, keySize)])
# Encrypted Key
enc_key = encrypt_app_aes(keyAes, keyTblAes[encKeyIdx*keySize:encKeyIdx*keySize + keySize], ivVal0)
am_print("Encrypted Key")
am_print([hex(enc_key[n]) for n in range (0, keySize)])
# Fill up the IV
for x in range(0, AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES):
hdr_binarray[AM_IMAGEHDR_OFFSET_IV + x] = ivValAes[x]
# Fill up the Encrypted Key
for x in range(0, keySize):
hdr_binarray[AM_IMAGEHDR_OFFSET_KEK + x] = enc_key[x]
else:
enc_binarray = hdr_binarray[AM_IMAGEHDR_START_ENCRYPT:hdr_length] + app_binarray
if (authI != 0): # Install Authentication needed
am_print("Install Authentication Enabled")
# am_print("Key used for HMAC")
# am_print([hex(keyTblHmac[authKeyIdx*AM_SECBOOT_KEYIDX_BYTES + n]) for n in range (0, AM_HMAC_SIG_SIZE)])
# Initialize the top level HMAC
sig = compute_hmac(keyTblHmac[authKeyIdx*AM_SECBOOT_KEYIDX_BYTES:(authKeyIdx*AM_SECBOOT_KEYIDX_BYTES+AM_HMAC_SIG_SIZE)], (hdr_binarray[AM_IMAGEHDR_START_HMAC_INST:AM_IMAGEHDR_START_ENCRYPT] + enc_binarray))
am_print("Generated Signature")
am_print([hex(n) for n in sig])
# Fill up the HMAC
for x in range(0, AM_HMAC_SIG_SIZE):
hdr_binarray[AM_IMAGEHDR_OFFSET_SIG + x] = sig[x]
# compute the CRC for the blob - this is done on a clear image
crc = crc32(hdr_binarray[AM_IMAGEHDR_START_CRC:hdr_length] + app_binarray)
am_print("crc = ",hex(crc));
w1 = crc
fill_word(hdr_binarray, AM_IMAGEHDR_OFFSET_CRC, w1)
# now output all three binary arrays in the proper order
output = output + '_OTA_blob.bin'
blob2wiredfile = output # save the output of bin2blob for use by blob2wired
am_print("Writing to file ", output)
with open(output, mode = 'wb') as out:
out.write(hdr_binarray[0:AM_IMAGEHDR_START_ENCRYPT])
out.write(enc_binarray)
#******************************************************************************
#
# Generate the image blob as per command line parameters
#
#******************************************************************************
def blob2wired_process(appFile, imagetype, loadaddress, authalgo, encalgo, authKeyIdx, encKeyIdx, optionsVal, maxSize, output):
global uploadbinfile
app_binarray = bytearray()
# Open the file, and read it into an array of integers.
print('testing: ' + appFile )
with open(appFile,'rb') as f_app:
app_binarray.extend(f_app.read())
f_app.close()
# Make sure it is page multiple
if ((maxSize & (FLASH_PAGE_SIZE - 1)) != 0):
am_print ("split needs to be multiple of flash page size", level=AM_PRINT_LEVEL_ERROR)
return
if (encalgo != 0):
if ((encKeyIdx < minAesKeyIdx) or (encKeyIdx > maxAesKeyIdx)):
am_print("Invalid encKey Idx ", encKeyIdx, level=AM_PRINT_LEVEL_ERROR)
return
if (encalgo == 2):
if (encKeyIdx & 0x1):
am_print("Invalid encKey Idx ", encKeyIdx, level=AM_PRINT_LEVEL_ERROR);
return
keySize = 32
else:
keySize = 16
if (authalgo != 0):
if ((authKeyIdx < minHmacKeyIdx) or (authKeyIdx > maxHmacKeyIdx) or (authKeyIdx & 0x1)):
am_print("Invalid authKey Idx ", authKeyIdx, level=AM_PRINT_LEVEL_ERROR);
return
hdr_length = AM_WU_IMAGEHDR_SIZE; #fixed header length
am_print("Header Size = ", hex(hdr_length))
orig_app_length = (len(app_binarray))
if (encalgo != 0):
block_size = keySize
app_binarray = pad_to_block_size(app_binarray, block_size, 1)
else:
# Add Padding
app_binarray = pad_to_block_size(app_binarray, 4, 0)
app_length = (len(app_binarray))
am_print("app_size ",hex(app_length), "(",app_length,")")
if (app_length + hdr_length > maxSize):
am_print("Image size bigger than max - Creating Split image")
start = 0
# now output all three binary arrays in the proper order
output = output + '_Wired_OTA_blob.bin'
uploadbinfile = output; # save the name of the output from blob2wired
out = open(output, mode = 'wb')
while (start < app_length):
#generate mutable byte array for the header
hdr_binarray = bytearray([0x00]*hdr_length);
if (app_length - start > maxSize):
end = start + maxSize
else:
end = app_length
if (imagetype == AM_SECBOOT_WIRED_IMAGETYPE_INFO0_NOOTA):
key = INFO_KEY
# word offset
fill_word(hdr_binarray, AM_WU_IMAGEHDR_OFFSET_ADDR, loadaddress>>2)
else:
key = FLASH_KEY
# load address
fill_word(hdr_binarray, AM_WU_IMAGEHDR_OFFSET_ADDR, loadaddress)
# Create imageType & options
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_IMAGETYPE] = imagetype
# Set the options only for the first block
if (start == 0):
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_OPTIONS] = optionsVal
else:
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_OPTIONS] = 0
# Create Info0 Update Blob for wired update
fill_word(hdr_binarray, AM_WU_IMAGEHDR_OFFSET_KEY, key)
# update size
fill_word(hdr_binarray, AM_WU_IMAGEHDR_OFFSET_SIZE, end-start)
w0 = ((authalgo & 0xf) | ((authKeyIdx << 8) & 0xf00) | ((encalgo << 16) & 0xf0000) | ((encKeyIdx << 24) & 0x0f000000))
fill_word(hdr_binarray, 0, w0)
if (encalgo != 0):
keyIdx = encKeyIdx - minAesKeyIdx
ivValAes = os.urandom(AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES)
am_print("Initialization Vector")
am_print([hex(n) for n in ivValAes])
keyAes = os.urandom(keySize)
am_print("AES Key used for encryption")
am_print([hex(keyAes[n]) for n in range (0, keySize)])
# Encrypted Part - after security header
enc_binarray = encrypt_app_aes((hdr_binarray[AM_WU_IMAGEHDR_START_ENCRYPT:hdr_length] + app_binarray[start:end]), keyAes, ivValAes)
# am_print("Key used for encrypting AES Key")
# am_print([hex(keyTblAes[keyIdx*AM_SECBOOT_KEYIDX_BYTES + n]) for n in range (0, keySize)])
# Encrypted Key
enc_key = encrypt_app_aes(keyAes, keyTblAes[keyIdx*AM_SECBOOT_KEYIDX_BYTES:(keyIdx*AM_SECBOOT_KEYIDX_BYTES + keySize)], ivVal0)
am_print("Encrypted Key")
am_print([hex(enc_key[n]) for n in range (0, keySize)])
# Fill up the IV
for x in range(0, AM_SECBOOT_AESCBC_BLOCK_SIZE_BYTES):
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_IV + x] = ivValAes[x]
# Fill up the Encrypted Key
for x in range(0, keySize):
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_KEK + x] = enc_key[x]
else:
enc_binarray = hdr_binarray[AM_WU_IMAGEHDR_START_ENCRYPT:hdr_length] + app_binarray[start:end]
if (authalgo != 0): # Authentication needed
keyIdx = authKeyIdx - minHmacKeyIdx
# am_print("Key used for HMAC")
# am_print([hex(keyTblHmac[keyIdx*AM_SECBOOT_KEYIDX_BYTES + n]) for n in range (0, AM_HMAC_SIG_SIZE)])
# Initialize the HMAC - Sign is computed on image following the signature
sig = compute_hmac(keyTblHmac[keyIdx*AM_SECBOOT_KEYIDX_BYTES:(keyIdx*AM_SECBOOT_KEYIDX_BYTES+AM_HMAC_SIG_SIZE)], hdr_binarray[AM_WU_IMAGEHDR_START_HMAC:AM_WU_IMAGEHDR_START_ENCRYPT] + enc_binarray)
am_print("HMAC")
am_print([hex(n) for n in sig])
# Fill up the HMAC
for x in range(0, AM_HMAC_SIG_SIZE):
hdr_binarray[AM_WU_IMAGEHDR_OFFSET_SIG + x] = sig[x]
am_print("Writing to file ", output)
am_print("Image from ", str(hex(start)), " to ", str(hex(end)), " will be loaded at", str(hex(loadaddress)))
out.write(hdr_binarray[0:AM_WU_IMAGEHDR_START_ENCRYPT])
out.write(enc_binarray)
# Reset start for next chunk
start = end
loadaddress = loadaddress + maxSize
#******************************************************************************
#
# Main function
#
#******************************************************************************
def upload(args, verboseprint):
global loadTries
global loadSuccess
# Open a serial port, and communicate with Device
#
# Max flashing time depends on the amount of SRAM available.
# For very large images, the flashing happens page by page.
# However if the image can fit in the free SRAM, it could take a long time
# for the whole image to be flashed at the end.
# The largest image which can be stored depends on the max SRAM.
# Assuming worst case ~100 ms/page of flashing time, and allowing for the
# image to be close to occupying full SRAM (256K) which is 128 pages.
connection_timeout = 5
print('Connecting over serial port {}...'.format(args.port), flush=True)
#Check to see if the com port is available
try:
with serial.Serial(args.port, args.baud, timeout=connection_timeout) as ser:
pass
except:
#Show a list of com ports and recommend one
print("Detected Serial Ports:")
devices = list_ports.comports()
port = None
for dev in devices:
print(dev.description)
# The SparkFun BlackBoard has CH340 in the description
if 'CH340' in dev.description:
print("The port you selected was not found. But we did detect a CH340 on " + dev.device + " so you might try again on that port.")
break
elif 'FTDI' in dev.description:
print("The port you selected was not found. But we did detect an FTDI on " + dev.device + " so you might try again on that port.")
break
elif 'USB Serial Device' in dev.description:
print("The port you selected was not found. But we did detect a USB Serial Device on " + dev.device + " so you might try again on that port.")
break
else:
print("Com Port not found - Did you select the right one?")
exit()
#Begin talking over com port
#The auto-bootload sequence is good but not fullproof. The bootloader
#fails to correctly catch the BOOT signal about 1 out of ten times.
#Auto-retry this number of times before we give up.
while loadTries < 3:
loadSuccess = False
with serial.Serial(args.port, args.baud, timeout=connection_timeout) as ser:
#DTR is driven low when serial port open. DTR has now pulled RST low.
time.sleep(0.005) #3ms and 10ms work well. Not 50, and not 0.
#Setting RTS/DTR high causes the bootload pin to go high, then fall across 100ms
ser.setDTR(0) #Set DTR high
ser.setRTS(0) #Set RTS high - support the CH340E
#Give bootloader a chance to run and check bootload pin before communication begins. But must initiate com before bootloader timeout of 250ms.
time.sleep(0.100) #100ms works well
ser.reset_input_buffer() # reset the input bufer to discard any UART traffic that the device may have generated
connect_device(ser, args, verboseprint)
if(loadSuccess == True):
print("Tries =", loadTries)
print('Upload complete!')
exit()
else:
print("Fail")
loadTries = loadTries + 1
print("Tries =", loadTries)
print("Upload failed")
exit()
#******************************************************************************
#
# Communicate with Device
#
# Given a serial port, connects to the target device using the
# UART.
#
#******************************************************************************
def connect_device(ser, args, verboseprint):
global loadSuccess
# Send Hello
#generate mutable byte array for the header
hello = bytearray([0x00]*4)
fill_word(hello, 0, ((8 << 16) | AM_SECBOOT_WIRED_MSGTYPE_HELLO))
verboseprint('Sending Hello.')
response = send_command(hello, 88, ser, verboseprint)
#Check if response failed
if response == False:
verboseprint("Failed to respond")
return
verboseprint("Received response for Hello")
word = word_from_bytes(response, 4)
if ((word & 0xFFFF) == AM_SECBOOT_WIRED_MSGTYPE_STATUS):
# Received Status
print("Bootloader connected")
verboseprint("Received Status")
verboseprint("length = ", hex((word >> 16)))
verboseprint("version = ", hex(word_from_bytes(response, 8)))
verboseprint("Max Storage = ", hex(word_from_bytes(response, 12)))
verboseprint("Status = ", hex(word_from_bytes(response, 16)))
verboseprint("State = ", hex(word_from_bytes(response, 20)))
verboseprint("AMInfo = ")
for x in range(24, 88, 4):
verboseprint(hex(word_from_bytes(response, x)))
abort = args.abort
if (abort != -1):
# Send OTA Desc
verboseprint('Sending Abort command.')
abortMsg = bytearray([0x00]*8);
fill_word(abortMsg, 0, ((12 << 16) | AM_SECBOOT_WIRED_MSGTYPE_ABORT))
fill_word(abortMsg, 4, abort)
if send_ackd_command(abortMsg, ser, verboseprint) == False:
verboseprint("Failed to ack command")
return
otadescaddr = args.otadesc
if (otadescaddr != 0xFFFFFFFF):
# Send OTA Desc
verboseprint('Sending OTA Descriptor = ', hex(otadescaddr))
otaDesc = bytearray([0x00]*8);
fill_word(otaDesc, 0, ((12 << 16) | AM_SECBOOT_WIRED_MSGTYPE_OTADESC))
fill_word(otaDesc, 4, otadescaddr)
if send_ackd_command(otaDesc, ser, verboseprint) == False:
verboseprint("Failed to ack command")
return
imageType = args.imagetype
if (uploadbinfile != ''):
# Read the binary file from the command line.
with open(uploadbinfile, mode='rb') as binfile:
application = binfile.read()
# Gather the important binary metadata.
totalLen = len(application)
# Send Update command
verboseprint('Sending Update Command.')
# It is assumed that maxSize is 256b multiple
maxImageSize = args.split
if ((maxImageSize & (FLASH_PAGE_SIZE - 1)) != 0):
verboseprint ("split needs to be multiple of flash page size")
return
# Each Block of image consists of AM_WU_IMAGEHDR_SIZE Bytes Image header and the Image blob
maxUpdateSize = AM_WU_IMAGEHDR_SIZE + maxImageSize
numUpdates = (totalLen + maxUpdateSize - 1) // maxUpdateSize # Integer division
verboseprint("number of updates needed = ", numUpdates)
end = totalLen
for numUpdates in range(numUpdates, 0 , -1):
start = (numUpdates-1)*maxUpdateSize
crc = crc32(application[start:end])
applen = end - start
verboseprint("Sending block of size ", str(hex(applen)), " from ", str(hex(start)), " to ", str(hex(end)))
end = end - applen
update = bytearray([0x00]*16);
fill_word(update, 0, ((20 << 16) | AM_SECBOOT_WIRED_MSGTYPE_UPDATE))
fill_word(update, 4, applen)
fill_word(update, 8, crc)
# Size = 0 => We're not piggybacking any data to IMAGE command
fill_word(update, 12, 0)
if send_ackd_command(update, ser, verboseprint) == False:
verboseprint("Failed to ack command")
return
# Loop over the bytes in the image, and send them to the target.
resp = 0
# Max chunk size is AM_MAX_UART_MSG_SIZE adjusted for the header for Data message
maxChunkSize = AM_MAX_UART_MSG_SIZE - 12
for x in range(0, applen, maxChunkSize):
# Split the application into chunks of maxChunkSize bytes.
# This is the max chunk size supported by the UART bootloader
if ((x + maxChunkSize) > applen):
chunk = application[start+x:start+applen]
# print(str(hex(start+x)), " to ", str(hex(applen)))
else:
chunk = application[start+x:start+x+maxChunkSize]
# print(str(hex(start+x)), " to ", str(hex(start + x + maxChunkSize)))
chunklen = len(chunk)
# Build a data packet with a "data command" a "length" and the actual
# payload bytes, and send it to the target.
dataMsg = bytearray([0x00]*8);
fill_word(dataMsg, 0, (((chunklen + 12) << 16) | AM_SECBOOT_WIRED_MSGTYPE_DATA))
# seqNo
fill_word(dataMsg, 4, x)
verboseprint("Sending Data Packet of length ", chunklen)
if send_ackd_command(dataMsg + chunk, ser, verboseprint) == False:
verboseprint("Failed to ack command")
return
if (args.raw != ''):
# Read the binary file from the command line.
with open(args.raw, mode='rb') as rawfile:
blob = rawfile.read()
# Send Raw command
verboseprint('Sending Raw Command.')
ser.write(blob)
if (args.reset != 0):
# Send reset
verboseprint('Sending Reset Command.')
resetmsg = bytearray([0x00]*8);
fill_word(resetmsg, 0, ((12 << 16) | AM_SECBOOT_WIRED_MSGTYPE_RESET))
# options
fill_word(resetmsg, 4, args.reset)
if send_ackd_command(resetmsg, ser, verboseprint) == False:
verboseprint("Failed to ack command")
return
#Success! We're all done
loadSuccess = True
else:
# Received Wrong message
verboseprint("Received Unknown Message")
word = word_from_bytes(response, 4)
verboseprint("msgType = ", hex(word & 0xFFFF))
verboseprint("Length = ", hex(word >> 16))
verboseprint([hex(n) for n in response])
#print("!!!Wired Upgrade Unsuccessful!!!....Terminating the script")
#exit()
#******************************************************************************
#
# Send ACK'd command
#
# Sends a command, and waits for an ACK.
#
#******************************************************************************
def send_ackd_command(command, ser, verboseprint):
response = send_command(command, 20, ser, verboseprint)
#Check if response failed
if response == False:
verboseprint("Response not valid")
return False #Return error
word = word_from_bytes(response, 4)
if ((word & 0xFFFF) == AM_SECBOOT_WIRED_MSGTYPE_ACK):
# Received ACK
if (word_from_bytes(response, 12) != AM_SECBOOT_WIRED_ACK_STATUS_SUCCESS):
verboseprint("Received NACK")
verboseprint("msgType = ", hex(word_from_bytes(response, 8)))
verboseprint("error = ", hex(word_from_bytes(response, 12)))
verboseprint("seqNo = ", hex(word_from_bytes(response, 16)))
#print("!!!Wired Upgrade Unsuccessful!!!....Terminating the script")
verboseprint("Upload failed: No ack to command")
return False #Return error
return response
#******************************************************************************
#
# Send command
#
# Sends a command, and waits for the response.
#
#******************************************************************************
def send_command(params, response_len, ser, verboseprint):
# Compute crc
crc = crc32(params)
# print([hex(n) for n in int_to_bytes(crc)])
# print([hex(n) for n in params])
# send crc first
ser.write(int_to_bytes(crc))
# Next, send the parameters.
ser.write(params)
response = ''
response = ser.read(response_len)
# Make sure we got the number of bytes we asked for.
if len(response) != response_len:
verboseprint('No response for command 0x{:08X}'.format(word_from_bytes(params, 0) & 0xFFFF))
n = len(response)
if (n != 0):
verboseprint("received bytes ", len(response))
verboseprint([hex(n) for n in response])
return False
return response
#******************************************************************************
#
# Send a command that uses an array of bytes as its parameters.
#
#******************************************************************************
def send_bytewise_command(command, params, response_len, ser):
# Send the command first.
ser.write(int_to_bytes(command))
# Next, send the parameters.
ser.write(params)
response = ''
response = ser.read(response_len)
# Make sure we got the number of bytes we asked for.
if len(response) != response_len:
print("Upload failed: No reponse to command")
verboseprint('No response for command 0x{:08X}'.format(command))
exit()
return response
#******************************************************************************
#
# Errors
#
#******************************************************************************
class BootError(Exception):
pass
class NoAckError(BootError):
pass
def parse_arguments():
parser = argparse.ArgumentParser(description =
'Combination script to upload application binaries to Artemis module. Includes:\n\t\'- bin2blob: create OTA blob from binary image\'\n\t\'- blob2wired: create wired update image from OTA blob\'\n\t\'- upload: send wired update image to Apollo3 Artemis module via serial port\'\n\nThere are many command-line arguments. They have been labeled by which steps they apply to\n')
parser.add_argument('-a', dest = 'abort', default=-1, type=int, choices = [0,1,-1],
help = 'upload: Should it send abort command? (0 = abort, 1 = abort and quit, -1 = no abort) (default is -1)')
parser.add_argument('--authalgo', dest = 'authalgo', type=auto_int, default=0, choices=range(0, AM_SECBOOT_AUTH_ALGO_MAX+1),
help = 'bin2blob, blob2wired: ' + str(helpAuthAlgo))
parser.add_argument('--authI', dest = 'authI', type=auto_int, default=0, choices=[0,1],
help = 'bin2blob: Install Authentication check enabled (Default = N)?')
parser.add_argument('--authB', dest = 'authB', type=auto_int, default=0, choices=[0,1],
help = 'bin2blob: Boot Authentication check enabled (Default = N)?')
parser.add_argument('--authkey', dest = 'authkey', type=auto_int, default=(minHmacKeyIdx), choices = range(minHmacKeyIdx, maxHmacKeyIdx + 1),
help = 'bin2blob, blob2wired: Authentication Key Idx? (' + str(minHmacKeyIdx) + ' to ' + str(maxHmacKeyIdx) + ')')
parser.add_argument('-b', dest='baud', default=115200, type=int,
help = 'upload: Baud Rate (default is 115200)')
parser.add_argument('--bin', dest='appFile', type=argparse.FileType('rb'),
help='bin2blob: binary file (blah.bin)')
parser.add_argument('-clean', dest='clean', default=0, type=int,
help = 'All: whether or not to remove intermediate files')
parser.add_argument('--child0', dest = 'child0', type=auto_int, default=hex(0xFFFFFFFF),
help = 'bin2blob: child (blobPtr#0 for Main / feature key for AM3P)')
parser.add_argument('--child1', dest = 'child1', type=auto_int, default=hex(0xFFFFFFFF),
help = 'bin2blob: child (blobPtr#1 for Main)')
parser.add_argument('--crcI', dest = 'crcI', type=auto_int, default=1, choices=[0,1],
help = 'bin2blob: Install CRC check enabled (Default = Y)?')
parser.add_argument('--crcB', dest = 'crcB', type=auto_int, default=0, choices=[0,1],
help = 'bin2blob: Boot CRC check enabled (Default = N)?')
parser.add_argument('--encalgo', dest = 'encalgo', type=auto_int, default=0, choices = range(0, AM_SECBOOT_ENC_ALGO_MAX+1),
help = 'bin2blob, blob2wired: ' + str(helpEncAlgo))
parser.add_argument('--erasePrev', dest = 'erasePrev', type=auto_int, default=0, choices=[0,1],
help = 'bin2blob: erasePrev (Valid only for main)')
# parser.add_argument('-f', dest='binfile', default='',
# help = 'upload: Binary file to program into the target device')
parser.add_argument('-i', dest = 'imagetype', default=AM_SECBOOT_WIRED_IMAGETYPE_INVALID, type=auto_int,
choices = [
(AM_SECBOOT_WIRED_IMAGETYPE_SBL),
(AM_SECBOOT_WIRED_IMAGETYPE_AM3P),
(AM_SECBOOT_WIRED_IMAGETYPE_PATCH),
(AM_SECBOOT_WIRED_IMAGETYPE_MAIN),
(AM_SECBOOT_WIRED_IMAGETYPE_CHILD),
(AM_SECBOOT_WIRED_IMAGETYPE_CUSTPATCH),
(AM_SECBOOT_WIRED_IMAGETYPE_NONSECURE),
(AM_SECBOOT_WIRED_IMAGETYPE_INFO0),
(AM_SECBOOT_WIRED_IMAGETYPE_INFO0_NOOTA),
(AM_SECBOOT_WIRED_IMAGETYPE_INVALID)
],
help = 'blob2wired, upload: ImageType ('
+ str(AM_SECBOOT_WIRED_IMAGETYPE_SBL) + ': SBL, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_AM3P) + ': AM3P, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_PATCH) + ': Patch, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_MAIN) + ': Main, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_CHILD) + ': Child, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_CUSTPATCH) + ': CustOTA, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_NONSECURE) + ': NonSecure, '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_INFO0) + ': Info0 '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_INFO0_NOOTA) + ': Info0_NOOTA) '
+ str(AM_SECBOOT_WIRED_IMAGETYPE_INVALID) + ': Invalid) '
'- default[Invalid]')
parser.add_argument('--kek', dest = 'kek', type=auto_int, default=(minAesKeyIdx), choices = range(minAesKeyIdx, maxAesKeyIdx+1),
help = 'KEK index? (' + str(minAesKeyIdx) + ' to ' + str(maxAesKeyIdx) + ')')
parser.add_argument('--load-address-wired', dest='loadaddress_blob', type=auto_int, default=hex(0x60000),
help='blob2wired: Load address of the binary - Where in flash the blob will be stored (could be different than install address of binary within).')
parser.add_argument('--load-address-blob', dest='loadaddress_image', type=auto_int, default=hex(AM_SECBOOT_DEFAULT_NONSECURE_MAIN),
help='bin2blob: Load address of the binary.')
parser.add_argument('--loglevel', dest='loglevel', type=auto_int, default=AM_PRINT_LEVEL_INFO,
choices = range(AM_PRINT_LEVEL_MIN, AM_PRINT_LEVEL_MAX+1),
help='bin2blob, blob2wired: ' + str(helpPrintLevel))
parser.add_argument('--magic-num', dest='magic_num', default=hex(AM_IMAGE_MAGIC_NONSECURE),
type=lambda x: x.lower(),
# type = str.lower,
choices = [
hex(AM_IMAGE_MAGIC_MAIN),
hex(AM_IMAGE_MAGIC_CHILD),
hex(AM_IMAGE_MAGIC_CUSTPATCH),
hex(AM_IMAGE_MAGIC_NONSECURE),
hex(AM_IMAGE_MAGIC_INFO0)
],
help = 'bin2blob: Magic Num ('
+ str(hex(AM_IMAGE_MAGIC_MAIN)) + ': Main, '
+ str(hex(AM_IMAGE_MAGIC_CHILD)) + ': Child, '
+ str(hex(AM_IMAGE_MAGIC_CUSTPATCH)) + ': CustOTA, '
+ str(hex(AM_IMAGE_MAGIC_NONSECURE)) + ': NonSecure, '
+ str(hex(AM_IMAGE_MAGIC_INFO0)) + ': Info0) '
'- default[Main]'
)
parser.add_argument('-o', dest = 'output', default='wuimage',
help = 'all: Output filename (without the extension) [also used for intermediate filenames]')
parser.add_argument('-ota', dest = 'otadesc', type=auto_int, default=0xFE000,
help = 'upload: OTA Descriptor Page address (hex) - (Default is 0xFE000 - at the end of main flash) - enter 0xFFFFFFFF to instruct SBL to skip OTA')
parser.add_argument('--options', dest = 'options', type=auto_int, default=0x1,
help = 'blob2wired: Options (16b hex value) - bit0 instructs to perform OTA of the image after wired download (set to 0 if only downloading & skipping OTA flow)')
parser.add_argument('-p', dest = 'protection', type=auto_int, default=0, choices = [0x0, 0x1, 0x2, 0x3],
help = 'bin2blob: protection info 2 bit C W')
parser.add_argument('-port', dest = 'port', help = 'upload: Serial COMx Port')
parser.add_argument('-r', dest = 'reset', default=1, type=auto_int, choices = [0,1,2],
help = 'upload: Should it send reset command after image download? (0 = no reset, 1 = POI, 2 = POR) (default is 1)')
parser.add_argument('--raw', dest='raw', default='',
help = 'upload: Binary file for raw message')
parser.add_argument('--split', dest='split', type=auto_int, default=hex(MAX_DOWNLOAD_SIZE),
help='blob2wired, upload: Specify the max block size if the image will be downloaded in pieces')
parser.add_argument('--version', dest = 'version', type=auto_int, default=0,
help = 'bin2blob: version (15 bit)')
parser.add_argument("-v", "--verbose", default=0, help="All: Enable verbose output",
action="store_true")
args = parser.parse_args()
args.magic_num = int(args.magic_num, 16)
return args
#******************************************************************************
#
# Main function.
#
#******************************************************************************
# example calling:
# python artemis_bin_to_board.py --bin application.bin --load-address-blob 0x20000 --magic-num 0xCB -o application --version 0x0 --load-address-wired 0xC000 -i 6 --options 0x1 -b 921600 -port COM4 -r 1 -v
def main():
# Read the arguments.
args = parse_arguments()
am_set_print_level(args.loglevel)
global blob2wiredfile
bin2blob_process(args.loadaddress_blob, args.appFile, args.magic_num, args.crcI, args.crcB, args.authI, args.authB, args.protection, args.authkey, args.output, args.kek, args.version, args.erasePrev, args.child0, args.child1, args.authalgo, args.encalgo)
blob2wired_process( blob2wiredfile, args.imagetype, args.loadaddress_image, args.authalgo, args.encalgo, args.authkey, args.kek, args.options, args.split, args.output)
# todo: link the bin2blob step with the blob2wired step by input/output files
#Create print function for verbose output if caller deems it: https://stackoverflow.com/questions/5980042/how-to-implement-the-verbose-or-v-option-into-a-script
if args.verbose:
def verboseprint(*args):
# Print each argument separately so caller doesn't need to
# stuff everything to be printed into a single string
for arg in args:
print(arg, end=''),
print()
else:
verboseprint = lambda *a: None # do-nothing function
upload(args, verboseprint)
if(args.clean == 1):
print('Cleaning up intermediate files') # todo: why isnt this showing w/ -clean option?
if __name__ == '__main__':
main()