diff --git a/bootloaders/cc2538-bsl.py b/bootloaders/cc2538-bsl.py index b30bf8c..0404d9d 100644 --- a/bootloaders/cc2538-bsl.py +++ b/bootloaders/cc2538-bsl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2014, Jelmer Tiete . # All rights reserved. @@ -37,23 +37,23 @@ # Make sure you don't lock yourself out!! (enable backdoor in your firmware) # More info at https://github.com/JelmerT/cc2538-bsl -from __future__ import print_function from subprocess import Popen, PIPE -import sys, getopt +import sys +import getopt import glob +import math import time -import tempfile import os -import subprocess import struct import binascii import traceback try: import magic + magic.from_file have_magic = True -except ImportError: +except (ImportError, AttributeError): have_magic = False try: @@ -62,27 +62,19 @@ except ImportError: have_hex_support = False -#version -VERSION_STRING = "2.0" +# version +__version__ = "2.1" # Verbose level QUIET = 5 -# Check which version of Python is running -PY3 = sys.version_info >= (3,0) - try: import serial except ImportError: print('{} requires the Python serial library'.format(sys.argv[0])) - print('Please install it with one of the following:') + print('Please install it with:') print('') - if PY3: - print(' Ubuntu: sudo apt-get install python3-serial') - print(' Mac: sudo port install py34-serial') - else: - print(' Ubuntu: sudo apt-get install python-serial') - print(' Mac: sudo port install py-serial') + print(' pip3 install pyserial') sys.exit(1) @@ -91,14 +83,16 @@ def mdebug(level, message, attr='\n'): print(message, end=attr, file=sys.stderr) # Takes chip IDs (obtained via Get ID command) to human-readable names -CHIP_ID_STRS = {0xb964: 'CC2538'} +CHIP_ID_STRS = {0xb964: 'CC2538', + 0xb965: 'CC2538' + } -RETURN_CMD_STRS = {0x40: 'Success', - 0x41: 'Unknown command', - 0x42: 'Invalid command', - 0x43: 'Invalid address', - 0x44: 'Flash fail' - } +RETURN_CMD_STRS = {0x40: 'Success', + 0x41: 'Unknown command', + 0x42: 'Invalid command', + 0x43: 'Invalid address', + 0x44: 'Flash fail' + } COMMAND_RET_SUCCESS = 0x40 COMMAND_RET_UNKNOWN_CMD = 0x41 @@ -106,44 +100,39 @@ def mdebug(level, message, attr='\n'): COMMAND_RET_INVALID_ADR = 0x43 COMMAND_RET_FLASH_FAIL = 0x44 + class CmdException(Exception): pass + class FirmwareFile(object): HEX_FILE_EXTENSIONS = ('hex', 'ihx', 'ihex') def __init__(self, path): """ Read a firmware file and store its data ready for device programming. - This class will try to guess the file type if python-magic is available. - If python-magic indicates a plain text file, and if IntelHex is available, then the file will be treated as one of Intel HEX format. - In all other cases, the file will be treated as a raw binary file. - In both cases, the file's contents are stored in bytes for subsequent usage to program a device or to perform a crc check. - Parameters: path -- A str with the path to the firmware file. - Attributes: - bytes: A bytearray with firmware contents ready to send to the device + bytes: A bytearray with firmware contents ready to send to the + device """ self._crc32 = None firmware_is_hex = False if have_magic: - file_type = bytearray(magic.from_file(path, True)) + file_type = magic.from_file(path, mime=True) - #from_file() returns bytes with PY3, str with PY2. This comparison - #will be True in both cases""" - if file_type == b'text/plain': + if file_type == 'text/plain': firmware_is_hex = True mdebug(5, "Firmware file: Intel Hex") - elif file_type == b'application/octet-stream': + elif file_type == 'application/octet-stream': mdebug(5, "Firmware file: Raw Binary") else: error_str = "Could not determine firmware type. Magic " \ @@ -178,7 +167,6 @@ def __init__(self, path): def crc32(self): """ Return the crc32 checksum of the firmware image - Return: The firmware's CRC32, ready for comparison with the CRC returned by the ROM bootloader's COMMAND_CRC32 @@ -188,20 +176,39 @@ def crc32(self): return self._crc32 + class CommandInterface(object): + ACK_BYTE = 0xCC NACK_BYTE = 0x33 - def open(self, aport='/dev/tty.usbserial-000013FAB', abaudrate=500000): - self.sp = serial.Serial( - port=aport, - baudrate=abaudrate, # baudrate - bytesize=8, # number of databits - parity=serial.PARITY_NONE, - stopbits=1, - xonxoff=0, # enable software flow control - rtscts=0, # disable RTS/CTS flow control - timeout=0.5 # set a timeout value, None for waiting forever - ) + + def open(self, aport=None, abaudrate=500000): + # Try to create the object using serial_for_url(), or fall back to the + # old serial.Serial() where serial_for_url() is not supported. + # serial_for_url() is a factory class and will return a different + # object based on the URL. For example serial_for_url("/dev/tty.") + # will return a serialposix.Serial object for Ubuntu or Mac OS; + # serial_for_url("COMx") will return a serialwin32.Serial oject for Windows OS. + # For that reason, we need to make sure the port doesn't get opened at + # this stage: We need to set its attributes up depending on what object + # we get. + try: + self.sp = serial.serial_for_url(aport, do_not_open=True, timeout=10) + except AttributeError: + self.sp = serial.Serial(port=None, timeout=10) + self.sp.port = aport + + if ((os.name == 'nt' and isinstance(self.sp, serial.serialwin32.Serial)) or \ + (os.name == 'posix' and isinstance(self.sp, serial.serialposix.Serial))): + self.sp.baudrate=abaudrate # baudrate + self.sp.bytesize=8 # number of databits + self.sp.parity=serial.PARITY_NONE # parity + self.sp.stopbits=1 # stop bits + self.sp.xonxoff=0 # s/w (XON/XOFF) flow control + self.sp.rtscts=0 # h/w (RTS/CTS) flow control + self.sp.timeout=0.5 # set the timeout value + + self.sp.open() def invoke_bootloader(self, dtr_active_high=False, inverted=False): # Use the DTR and RTS lines to control bootloader and the !RESET pin. @@ -223,9 +230,10 @@ def invoke_bootloader(self, dtr_active_high=False, inverted=False): set_reset_pin(0) set_reset_pin(1) set_reset_pin(0) - time.sleep(0.002) # Make sure the pin is still asserted when the chip - # comes out of reset. This fixes an issue where there - # wasn't enough delay here on Mac. + # Make sure the pin is still asserted when the chip + # comes out of reset. This fixes an issue where + # there wasn't enough delay here on Mac. + time.sleep(0.002) set_bootloader_pin(0 if not dtr_active_high else 1) # Some boards have a co-processor that detects this sequence here and @@ -235,14 +243,13 @@ def invoke_bootloader(self, dtr_active_high=False, inverted=False): # need a small delay so as to avoid trying to talk to main chip before # it has actually entered its bootloader mode. # - - time.sleep(0.002) + # See contiki-os/contiki#1533 + time.sleep(0.1) def close(self): self.sp.close() - - def _wait_for_ack(self, info = "", timeout = 1): + def _wait_for_ack(self, info="", timeout=1): stop = time.time() + timeout got = bytearray(2) while got[-2] != 00 or got[-1] not in (CommandInterface.ACK_BYTE, @@ -276,41 +283,31 @@ def _encode_addr(self, addr): byte2 = (addr >> 8) & 0xFF byte1 = (addr >> 16) & 0xFF byte0 = (addr >> 24) & 0xFF - if PY3: - return bytes([byte0, byte1, byte2, byte3]) - else: - return (chr(byte0) + chr(byte1) + chr(byte2) + chr(byte3)) + return bytes([byte0, byte1, byte2, byte3]) def _decode_addr(self, byte0, byte1, byte2, byte3): return ((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | (byte0 << 0)) def _calc_checks(self, cmd, addr, size): - return ((sum(bytearray(self._encode_addr(addr))) - +sum(bytearray(self._encode_addr(size))) - +cmd) - &0xFF) + return ((sum(bytearray(self._encode_addr(addr))) + + sum(bytearray(self._encode_addr(size))) + + cmd) & 0xFF) def _write(self, data, is_retry=False): - if PY3: - if type(data) == int: - assert data < 256 - goal = 1 - written = self.sp.write(bytes([data])) - elif type(data) == bytes or type(data) == bytearray: - goal = len(data) - written = self.sp.write(data) - else: - raise CmdException("Internal Error. Bad data type: {}".format(type(data))) + if type(data) == int: + assert data < 256 + goal = 1 + written = self.sp.write(bytes([data])) + elif type(data) == bytes or type(data) == bytearray: + goal = len(data) + written = self.sp.write(data) else: - if type(data) == int: - assert data < 256 - goal = 1 - written = self.sp.write(chr(data)) - else: - goal = len(data) - written = self.sp.write(data) + raise CmdException("Internal Error. Bad data type: {}" + .format(type(data))) + if written < goal: - mdebug(10, "*** Only wrote {} of target {} bytes".format(written, goal)) + mdebug(10, "*** Only wrote {} of target {} bytes" + .format(written, goal)) if is_retry and written == 0: raise CmdException("Failed to write data on the serial bus") mdebug(10, "*** Retrying write for remainder") @@ -332,7 +329,6 @@ def sendNAck(self): self._write(0x33) return - def receivePacket(self): # stop = time.time() + 5 # got = None @@ -344,34 +340,36 @@ def receivePacket(self): # if not got: # raise CmdException("No response to %s" % info) - size = got[0] #rcv size - chks = got[1] #rcv checksum - data = bytearray(self._read(size - 2)) # rcv data + size = got[0] # rcv size + chks = got[1] # rcv checksum + data = bytearray(self._read(size - 2)) # rcv data mdebug(10, "*** received %x bytes" % size) - if chks == sum(data)&0xFF: + if chks == sum(data) & 0xFF: self.sendAck() return data else: self.sendNAck() - #TODO: retry receiving! + # TODO: retry receiving! raise CmdException("Received packet checksum error") return 0 def sendSynch(self): cmd = 0x55 - self.sp.flushInput() #flush serial input buffer for first ACK reception + # flush serial input buffer for first ACK reception + self.sp.flushInput() mdebug(10, "*** sending synch sequence") - self._write(cmd) # send U - self._write(cmd) # send U + self._write(cmd) # send U + self._write(cmd) # send U return self._wait_for_ack("Synch (0x55 0x55)", 2) def checkLastCmd(self): stat = self.cmdGetStatus() if not (stat): - raise CmdException("No response from target on status request. (Did you disable the bootloader?)") + raise CmdException("No response from target on status request. " + "(Did you disable the bootloader?)") if stat[0] == COMMAND_RET_SUCCESS: mdebug(10, "Command Successful") @@ -379,19 +377,19 @@ def checkLastCmd(self): else: stat_str = RETURN_CMD_STRS.get(stat[0], None) if stat_str is None: - mdebug(0, 'Warning: unrecognized status returned 0x%x' % stat[0]) + mdebug(0, "Warning: unrecognized status returned " + "0x%x" % stat[0]) else: mdebug(0, "Target returned: 0x%x, %s" % (stat[0], stat_str)) return 0 - def cmdPing(self): cmd = 0x20 lng = 3 - self._write(lng) # send size - self._write(cmd) # send checksum - self._write(cmd) # send data + self._write(lng) # send size + self._write(cmd) # send checksum + self._write(cmd) # send data mdebug(10, "*** Ping command (0x20)") if self._wait_for_ack("Ping (0x20)"): @@ -401,9 +399,9 @@ def cmdReset(self): cmd = 0x25 lng = 3 - self._write(lng) # send size - self._write(cmd) # send checksum - self._write(cmd) # send data + self._write(lng) # send size + self._write(cmd) # send checksum + self._write(cmd) # send data mdebug(10, "*** Reset command (0x25)") if self._wait_for_ack("Reset (0x25)"): @@ -413,15 +411,17 @@ def cmdGetChipId(self): cmd = 0x28 lng = 3 - self._write(lng) # send size - self._write(cmd) # send checksum - self._write(cmd) # send data + self._write(lng) # send size + self._write(cmd) # send checksum + self._write(cmd) # send data mdebug(10, "*** GetChipId command (0x28)") if self._wait_for_ack("Get ChipID (0x28)"): - version = self.receivePacket() # 4 byte answ, the 2 LSB hold chip ID + # 4 byte answ, the 2 LSB hold chip ID + version = self.receivePacket() if self.checkLastCmd(): - assert len(version) == 4, "Unreasonable chip id: %s" % repr(version) + assert len(version) == 4, ("Unreasonable chip " + "id: %s" % repr(version)) mdebug(10, " Version 0x%02X%02X%02X%02X" % tuple(version)) chip_id = (version[2] << 8) | version[3] return chip_id @@ -432,9 +432,9 @@ def cmdGetStatus(self): cmd = 0x23 lng = 3 - self._write(lng) # send size - self._write(cmd) # send checksum - self._write(cmd) # send data + self._write(lng) # send size + self._write(cmd) # send checksum + self._write(cmd) # send data mdebug(10, "*** GetStatus command (0x23)") if self._wait_for_ack("Get Status (0x23)"): @@ -445,9 +445,9 @@ def cmdSetXOsc(self): cmd = 0x29 lng = 3 - self._write(lng) # send size - self._write(cmd) # send checksum - self._write(cmd) # send data + self._write(lng) # send size + self._write(cmd) # send checksum + self._write(cmd) # send data mdebug(10, "*** SetXOsc command (0x29)") if self._wait_for_ack("SetXOsc (0x29)"): @@ -455,133 +455,136 @@ def cmdSetXOsc(self): # UART speed (needs) to be changed! def cmdRun(self, addr): - cmd=0x22 - lng=7 + cmd = 0x22 + lng = 7 - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,0)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, 0)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr mdebug(10, "*** Run command(0x22)") return 1 def cmdEraseMemory(self, addr, size): - cmd=0x26 - lng=11 + cmd = 0x26 + lng = 11 - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,size)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(self._encode_addr(size)) # send size + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, size)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(self._encode_addr(size)) # send size mdebug(10, "*** Erase command(0x26)") - if self._wait_for_ack("Erase memory (0x26)",10): + if self._wait_for_ack("Erase memory (0x26)", 10): return self.checkLastCmd() def cmdBankErase(self): cmd = 0x2C lng = 3 - self._write(lng) # send length - self._write(cmd) # send checksum - self._write(cmd) # send cmd + self._write(lng) # send length + self._write(cmd) # send checksum + self._write(cmd) # send cmd mdebug(10, "*** Bank Erase command(0x2C)") - if self._wait_for_ack("Bank Erase (0x2C)",10): + if self._wait_for_ack("Bank Erase (0x2C)", 10): return self.checkLastCmd() def cmdCRC32(self, addr, size): - cmd=0x27 - lng=11 + cmd = 0x27 + lng = 11 - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,size)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(self._encode_addr(size)) # send size + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, size)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(self._encode_addr(size)) # send size mdebug(10, "*** CRC32 command(0x27)") - if self._wait_for_ack("Get CRC32 (0x27)",1): - crc=self.receivePacket() + if self._wait_for_ack("Get CRC32 (0x27)", 1): + crc = self.receivePacket() if self.checkLastCmd(): - return self._decode_addr(crc[3],crc[2],crc[1],crc[0]) + return self._decode_addr(crc[3], crc[2], crc[1], crc[0]) def cmdCRC32CC26xx(self, addr, size): cmd = 0x27 lng = 15 - self._write(lng) # send length - self._write(self._calc_checks(cmd, addr, size)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(self._encode_addr(size)) # send size - self._write(self._encode_addr(0x00000000)) # send number of reads + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, size)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(self._encode_addr(size)) # send size + self._write(self._encode_addr(0x00000000)) # send number of reads mdebug(10, "*** CRC32 command(0x27)") if self._wait_for_ack("Get CRC32 (0x27)", 1): - crc=self.receivePacket() + crc = self.receivePacket() if self.checkLastCmd(): return self._decode_addr(crc[3], crc[2], crc[1], crc[0]) def cmdDownload(self, addr, size): - cmd=0x21 - lng=11 + cmd = 0x21 + lng = 11 - if (size % 4) != 0: # check for invalid data lengths - raise Exception('Invalid data size: %i. Size must be a multiple of 4.' % size) + if (size % 4) != 0: # check for invalid data lengths + raise Exception('Invalid data size: %i. ' + 'Size must be a multiple of 4.' % size) - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,size)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(self._encode_addr(size)) # send size + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, size)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(self._encode_addr(size)) # send size mdebug(10, "*** Download command (0x21)") - if self._wait_for_ack("Download (0x21)",2): + if self._wait_for_ack("Download (0x21)", 2): return self.checkLastCmd() def cmdSendData(self, data): - cmd=0x24 - lng=len(data)+3 + cmd = 0x24 + lng = len(data)+3 # TODO: check total size of data!! max 252 bytes! - self._write(lng) # send size - self._write((sum(bytearray(data))+cmd)&0xFF) # send checksum - self._write(cmd) # send cmd - self._write(bytearray(data)) # send data + self._write(lng) # send size + self._write((sum(bytearray(data))+cmd) & 0xFF) # send checksum + self._write(cmd) # send cmd + self._write(bytearray(data)) # send data mdebug(10, "*** Send Data (0x24)") - if self._wait_for_ack("Send data (0x24)",10): + if self._wait_for_ack("Send data (0x24)", 10): return self.checkLastCmd() - def cmdMemRead(self, addr): # untested - cmd=0x2A - lng=8 + def cmdMemRead(self, addr): # untested + cmd = 0x2A + lng = 8 - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,4)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(4) # send width, 4 bytes + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, 4)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(4) # send width, 4 bytes mdebug(10, "*** Mem Read (0x2A)") - if self._wait_for_ack("Mem Read (0x2A)",1): + if self._wait_for_ack("Mem Read (0x2A)", 1): data = self.receivePacket() if self.checkLastCmd(): - return data # self._decode_addr(ord(data[3]),ord(data[2]),ord(data[1]),ord(data[0])) + # self._decode_addr(ord(data[3]), + # ord(data[2]),ord(data[1]),ord(data[0])) + return data def cmdMemReadCC26xx(self, addr): cmd = 0x2A lng = 9 - self._write(lng) # send length - self._write(self._calc_checks(cmd, addr, 2)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(1) # send width, 4 bytes - self._write(1) # send number of reads + self._write(lng) # send length + self._write(self._calc_checks(cmd, addr, 2)) # send checksum + self._write(cmd) # send cmd + self._write(self._encode_addr(addr)) # send addr + self._write(1) # send width, 4 bytes + self._write(1) # send number of reads mdebug(10, "*** Mem Read (0x2A)") if self._wait_for_ack("Mem Read (0x2A)", 1): @@ -589,55 +592,74 @@ def cmdMemReadCC26xx(self, addr): if self.checkLastCmd(): return data - def cmdMemWrite(self, addr, data, width): # untested - # TODO: check width for 1 or 4 and data size - cmd=0x2B - lng=10 + def cmdMemWrite(self, addr, data, width): + if width != len(data): + raise ValueError("width does not match len(data)") + if width != 1 and width != 4: + raise ValueError("width must be 1 or 4") + + cmd = 0x2B + lng = 8 + len(data) + + content = ( + bytearray([cmd]) + + self._encode_addr(addr) + + bytearray([1 if (width == 4) else 0]) + + bytearray(data) + ) - self._write(lng) # send length - self._write(self._calc_checks(cmd,addr,0)) # send checksum - self._write(cmd) # send cmd - self._write(self._encode_addr(addr)) # send addr - self._write(bytearray(data)) # send data - self._write(width) # send width, 4 bytes + self._write(lng) # send length + self._write(sum(content) & 0xFF) # send checksum + self._write(content) mdebug(10, "*** Mem write (0x2B)") - if self._wait_for_ack("Mem Write (0x2B)",2): - return checkLastCmd() + if self._wait_for_ack("Mem Write (0x2B)", 2): + return self.checkLastCmd() # Complex commands section def writeMemory(self, addr, data): lng = len(data) - trsf_size = 248 # amount of data bytes transferred per packet (theory: max 252 + 3) + # amount of data bytes transferred per packet (theory: max 252 + 3) + trsf_size = 248 empty_packet = bytearray((0xFF,) * trsf_size) # Boot loader enable check - # TODO: implement check for all chip sizes & take into account partial firmware uploads - if (lng == 524288): #check if file is for 512K model - if not ((data[524247] & (1 << 4)) >> 4): #check the boot loader enable bit (only for 512K model) - if not ( conf['force'] or query_yes_no("The boot loader backdoor is not enabled "\ - "in the firmware you are about to write to the target. "\ - "You will NOT be able to reprogram the target using this tool if you continue! "\ - "Do you want to continue?","no") ): + # TODO: implement check for all chip sizes & take into account partial + # firmware uploads + if (lng == 524288): # check if file is for 512K model + # check the boot loader enable bit (only for 512K model) + if not ((data[524247] & (1 << 4)) >> 4): + if not (conf['force'] or + query_yes_no("The boot loader backdoor is not enabled " + "in the firmware you are about to write " + "to the target. You will NOT be able to " + "reprogram the target using this tool if " + "you continue! " + "Do you want to continue?", "no")): raise Exception('Aborted by user.') mdebug(5, "Writing %(lng)d bytes starting at address 0x%(addr)08X" % - { 'lng': lng, 'addr': addr}) + {'lng': lng, 'addr': addr}) offs = 0 addr_set = 0 - while lng > trsf_size: #check if amount of remaining data is less then packet size - if data[offs:offs+trsf_size] != empty_packet: #skip packets filled with 0xFF + # check if amount of remaining data is less then packet size + while lng > trsf_size: + # skip packets filled with 0xFF + if data[offs:offs+trsf_size] != empty_packet: if addr_set != 1: - self.cmdDownload(addr,lng) #set starting address if not set + # set starting address if not set + self.cmdDownload(addr, lng) addr_set = 1 - mdebug(5, " Write %(len)d bytes at 0x%(addr)08X" % {'addr': addr, 'len': trsf_size}, '\r') + mdebug(5, " Write %(len)d bytes at 0x%(addr)08X" + % {'addr': addr, 'len': trsf_size}, '\r') sys.stdout.flush() - self.cmdSendData(data[offs:offs+trsf_size]) # send next data packet + # send next data packet + self.cmdSendData(data[offs:offs+trsf_size]) else: # skipped packet, address needs to be set addr_set = 0 @@ -645,9 +667,11 @@ def writeMemory(self, addr, data): addr = addr + trsf_size lng = lng - trsf_size - mdebug(5, "Write %(len)d bytes at 0x%(addr)08X" % {'addr': addr, 'len': lng}) - self.cmdDownload(addr,lng) - return self.cmdSendData(data[offs:offs+lng]) # send last data packet + mdebug(5, "Write %(len)d bytes at 0x%(addr)08X" % {'addr': addr, + 'len': lng}) + self.cmdDownload(addr, lng) + return self.cmdSendData(data[offs:offs+lng]) # send last data packet + class Chip(object): def __init__(self, command_interface): @@ -656,26 +680,43 @@ def __init__(self, command_interface): # Some defaults. The child can override. self.flash_start_addr = 0x00000000 self.has_cmd_set_xosc = False + self.page_size = 2048 + + + def page_align_up(self, value): + return int(math.ceil(value / self.page_size) * self.page_size) + + + def page_align_down(self, value): + return int(math.floor(value / self.page_size) * self.page_size) + + + def page_to_addr(self, pages): + addresses = [] + for page in pages: + addresses.append(int(device.flash_start_addr) + + int(page)*self.page_size) + return addresses def crc(self, address, size): return getattr(self.command_interface, self.crc_cmd)(address, size) def disable_bootloader(self): - if not (conf['force'] or query_yes_no("Disabling the bootloader will prevent you from "\ - "using this script until you re-enable the bootloader "\ - "using JTAG. Do you want to continue?", "no")): + if not (conf['force'] or + query_yes_no("Disabling the bootloader will prevent you from " + "using this script until you re-enable the " + "bootloader using JTAG. Do you want to continue?", + "no")): raise Exception('Aborted by user.') - if PY3: - pattern = struct.pack('> 4 + if 0 < self.size <= 4: + self.size *= 0x20000 # in bytes + else: + self.size = 0x10000 # in bytes self.bootloader_address = self.flash_start_addr + self.size - ccfg_len - sram = 16 + ((((model[2] << 8) | model[3]) & 0x380) >> 5) # in KB + sram = (((model[2] << 8) | model[3]) & 0x380) >> 7 + sram = (2 - sram) << 3 if sram <= 1 else 32 # in KB pg = self.command_interface.cmdMemRead(FLASH_CTRL_DIECFG2) pg_major = (pg[2] & 0xF0) >> 4 + if pg_major == 0: + pg_major = 1 pg_minor = pg[2] & 0x0F - ieee_addr = self.command_interface.cmdMemRead(addr_ieee_address_primary + 4) - ieee_addr += self.command_interface.cmdMemRead(addr_ieee_address_primary) + ti_oui = bytearray([0x00, 0x12, 0x4B]) + ieee_addr = self.command_interface.cmdMemRead( + addr_ieee_address_primary) + ieee_addr_end = self.command_interface.cmdMemRead( + addr_ieee_address_primary + 4) + if ieee_addr[:3] == ti_oui: + ieee_addr += ieee_addr_end + else: + ieee_addr = ieee_addr_end + ieee_addr mdebug(5, "CC2538 PG%d.%d: %dKB Flash, %dKB SRAM, CCFG at 0x%08X" % (pg_major, pg_minor, self.size >> 10, sram, self.bootloader_address)) - mdebug(5, "Primary IEEE Address: %s" % (':'.join('%02X' % x for x in ieee_addr))) + mdebug(5, "Primary IEEE Address: %s" + % (':'.join('%02X' % x for x in ieee_addr))) def erase(self): - mdebug(5, "Erasing %s bytes starting at address 0x%08X" % (self.size, self.flash_start_addr)) - return self.command_interface.cmdEraseMemory(self.flash_start_addr, self.size) + mdebug(5, "Erasing %s bytes starting at address 0x%08X" + % (self.size, self.flash_start_addr)) + return self.command_interface.cmdEraseMemory(self.flash_start_addr, + self.size) def read_memory(self, addr): # CC2538's COMMAND_MEMORY_READ sends each 4-byte number in inverted @@ -719,6 +777,7 @@ def read_memory(self, addr): data = self.command_interface.cmdMemRead(addr) return bytearray([data[x] for x in range(3, -1, -1)]) + class CC26xx(Chip): # Class constants MISC_CONF_1 = 0x500010A0 @@ -730,6 +789,7 @@ def __init__(self, command_interface): super(CC26xx, self).__init__(command_interface) self.bootloader_dis_val = 0x00000000 self.crc_cmd = "cmdCRC32CC26xx" + self.page_size = 4096 ICEPICK_DEVICE_ID = 0x50001318 FCFG_USER_ID = 0x50001294 @@ -751,7 +811,14 @@ def __init__(self, command_interface): # Read FCFG1_USER_ID to get the package and supported protocols user_id = self.command_interface.cmdMemReadCC26xx(FCFG_USER_ID) - package = {0x00: '4x4mm', 0x01: '5x5mm', 0x02: '7x7mm'}.get(user_id[2] & 0x03, "Unknown") + package = {0x00: '4x4mm', + 0x01: '5x5mm', + 0x02: '7x7mm', + 0x03: 'Wafer', + 0x04: '2.7x2.7', + 0x05: '7x7mm Q1', + }.get(user_id[2] & 0x03, "Unknown") + protocols = user_id[1] >> 4 # We can now detect the exact device @@ -759,14 +826,20 @@ def __init__(self, command_interface): chip = self._identify_cc26xx(pg_rev, protocols) elif wafer_id == 0xB9BE: chip = self._identify_cc13xx(pg_rev, protocols) + elif wafer_id == 0xBB41: + chip = self._identify_cc13xx(pg_rev, protocols) + self.page_size = 8192 # Read flash size, calculate and store bootloader disable address - self.size = self.command_interface.cmdMemReadCC26xx(FLASH_SIZE)[0] * 4096 + self.size = self.command_interface.cmdMemReadCC26xx( + FLASH_SIZE)[0] * self.page_size self.bootloader_address = self.size - ccfg_len + bootloader_dis_offset - self.addr_ieee_address_secondary = self.size - ccfg_len + ieee_address_secondary_offset + self.addr_ieee_address_secondary = (self.size - ccfg_len + + ieee_address_secondary_offset) # RAM size - ramhwopt_size = self.command_interface.cmdMemReadCC26xx(PRCM_RAMHWOPT)[0] & 3 + ramhwopt_size = self.command_interface.cmdMemReadCC26xx( + PRCM_RAMHWOPT)[0] & 3 if ramhwopt_size == 3: sram = "20KB" elif ramhwopt_size == 2: @@ -775,13 +848,16 @@ def __init__(self, command_interface): sram = "Unknown" # Primary IEEE address. Stored with the MSB at the high address - ieee_addr = self.command_interface.cmdMemReadCC26xx(addr_ieee_address_primary + 4)[::-1] - ieee_addr += self.command_interface.cmdMemReadCC26xx(addr_ieee_address_primary)[::-1] + ieee_addr = self.command_interface.cmdMemReadCC26xx( + addr_ieee_address_primary + 4)[::-1] + ieee_addr += self.command_interface.cmdMemReadCC26xx( + addr_ieee_address_primary)[::-1] mdebug(5, "%s (%s): %dKB Flash, %s SRAM, CCFG.BL_CONFIG at 0x%08X" % (chip, package, self.size >> 10, sram, self.bootloader_address)) - mdebug(5, "Primary IEEE Address: %s" % (':'.join('%02X' % x for x in ieee_addr))) + mdebug(5, "Primary IEEE Address: %s" + % (':'.join('%02X' % x for x in ieee_addr))) def _identify_cc26xx(self, pg, protocols): chips_dict = { @@ -798,11 +874,20 @@ def _identify_cc26xx(self, pg, protocols): pg_str = "PG2.0" elif pg == 7: pg_str = "PG2.1" - elif pg == 8: - rev_minor = self.command_interface.cmdMemReadCC26xx(CC26xx.MISC_CONF_1)[0] + elif pg == 8 or pg == 0x0B: + # CC26x0 PG2.2+ or CC26x0R2 + rev_minor = self.command_interface.cmdMemReadCC26xx( + CC26xx.MISC_CONF_1)[0] if rev_minor == 0xFF: rev_minor = 0x00 - pg_str = "PG2.%d" % (2 + rev_minor,) + + if pg == 8: + # CC26x0 + pg_str = "PG2.%d" % (2 + rev_minor,) + elif pg == 0x0B: + # HW revision R2, update Chip name + chip_str += 'R2' + pg_str = "PG%d.%d" % (1 + (rev_minor // 10), rev_minor % 10) return "%s %s" % (chip_str, pg_str) @@ -813,8 +898,9 @@ def _identify_cc13xx(self, pg, protocols): if pg == 0: pg_str = "PG1.0" - elif pg == 2: - rev_minor = self.command_interface.cmdMemReadCC26xx(CC26xx.MISC_CONF_1)[0] + elif pg == 2 or pg == 3: + rev_minor = self.command_interface.cmdMemReadCC26xx( + CC26xx.MISC_CONF_1)[0] if rev_minor == 0xFF: rev_minor = 0x00 pg_str = "PG2.%d" % (rev_minor,) @@ -830,10 +916,14 @@ def read_memory(self, addr): # they are stored on the device return self.command_interface.cmdMemReadCC26xx(addr) + def query_yes_no(question, default="yes"): - valid = {"yes":True, "y":True, "ye":True, - "no":False, "n":False} - if default == None: + valid = {"yes": True, + "y": True, + "ye": True, + "no": False, + "n": False} + if default is None: prompt = " [y/n] " elif default == "yes": prompt = " [Y/n] " @@ -844,20 +934,18 @@ def query_yes_no(question, default="yes"): while True: sys.stdout.write(question + prompt) - if PY3: - choice = input().lower() - else: - choice = raw_input().lower() + choice = input().lower() if default is not None and choice == '': return valid[default] elif choice in valid: return valid[choice] else: - sys.stdout.write("Please respond with 'yes' or 'no' "\ + sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + # Convert the entered IEEE address into an integer -def parse_ieee_address (inaddr): +def parse_ieee_address(inaddr): try: return int(inaddr, 16) except ValueError: @@ -869,13 +957,57 @@ def parse_ieee_address (inaddr): if len(bytes) != 8: raise ValueError("Supplied IEEE address does not contain 8 bytes") addr = 0 - for i,b in zip(range(8), bytes): + for i, b in zip(range(8), bytes): try: addr += int(b, 16) << (56-(i*8)) except ValueError: raise ValueError("IEEE address contains invalid bytes") return addr + +def _parse_range_values(device, values): + if len(values) and len(values) < 3: + page_addr_range = [] + try: + for value in values: + try: + if int(value) % int(device.page_size) != 0: + raise ValueError("Supplied addresses are not page_size: " + "{} aligned".format(device.page_size)) + page_addr_range.append(int(value)) + except ValueError: + if int(value, 16) % int(device.page_size) != 0: + raise ValueError("Supplied addresses are not page_size: " + "{} aligned".format(device.page_size)) + page_addr_range.append(int(value, 16)) + return page_addr_range + except ValueError: + raise ValueError("Supplied value is not a page or an address") + else: + raise ValueError("Supplied range is neither a page or address range") + + +def parse_page_address_range(device, pg_range): + """Convert the address/page range into a start address and byte length""" + values = pg_range.split(',') + page_addr = [] + # check if first argument is character + if values[0].isalpha(): + values[0].lower() + if values[0] == 'p' or values[0] == 'page': + if values[0] == 'p': + values[1:] = device.page_to_addr(values[1:]) + elif values[0] != 'a' and values[0] != 'address': + raise ValueError("Prefix is neither a(address) or p(page)") + page_addr.extend(_parse_range_values(device, values[1:])) + else: + page_addr.extend(_parse_range_values(device, values)) + if len(page_addr) == 1: + return [page_addr[0], device.page_size] + else: + return [page_addr[0], (page_addr[1] - page_addr[0])] + + def print_version(): # Get the version using "git describe". try: @@ -883,65 +1015,80 @@ def print_version(): stdout=PIPE, stderr=PIPE) p.stderr.close() line = p.stdout.readlines()[0] - version = line.strip() + version = line.decode('utf-8').strip() except: - # We're not in a git repo, or git failed, use fixed version string. - version = VERSION_STRING + # We're not in a git repo, or git failed, use fixed version string. + version = __version__ print('%s %s' % (sys.argv[0], version)) -def usage(): - print("""Usage: %s [-DhqVfewvr] [-l length] [-p port] [-b baud] [-a addr] [-i addr] [--bootloader-active-high] [--bootloader-invert-lines] [file.bin] - -h, --help This help - -q Quiet - -V Verbose - -f Force operation(s) without asking any questions - -e Erase (full) - -w Write - -v Verify (CRC32 check) - -r Read - -l length Length of read - -p port Serial port (default: first USB-like port in /dev) - -b baud Baud speed (default: 500000) - -a addr Target address - -i, --ieee-address addr Set the secondary 64 bit IEEE address - --bootloader-active-high Use active high signals to enter bootloader - --bootloader-invert-lines Inverts the use of RTS and DTR to enter bootloader - -D, --disable-bootloader After finishing, disable the bootloader - --version Print script version +def usage(): + print("""Usage: %s [-DhqVfewvr] [-l length] [-p port] [-b baud] [-a addr] \ + [-i addr] [--bootloader-active-high] [--bootloader-invert-lines] [file.bin] + -h, --help This help + -q Quiet + -V Verbose + -f Force operation(s) without asking any questions + -e Mass erase + -E, --erase-page p/a,range Receives an address(a) range or page(p) range, + default is address(a) + eg: -E a,0x00000000,0x00001000, + -E p,1,4 + -w Write + -W, --erase-write Write after erasing section to write to (avoids + a mass erase). Rounds up section to erase if + not page aligned. + -v Verify (CRC32 check) + -r Read + -l length Length of read + -p port Serial port (default: first USB-like port in /dev) + -b baud Baud speed (default: 500000) + -a addr Target address + -i, --ieee-address addr Set the secondary 64 bit IEEE address + --bootloader-active-high Use active high signals to enter bootloader + --bootloader-invert-lines Inverts the use of RTS and DTR to enter bootloader + -D, --disable-bootloader After finishing, disable the bootloader + --version Print script version Examples: ./%s -e -w -v example/main.bin ./%s -e -w -v --ieee-address 00:12:4b:aa:bb:cc:dd:ee example/main.bin - - """ % (sys.argv[0],sys.argv[0],sys.argv[0])) + """ % (sys.argv[0], sys.argv[0], sys.argv[0])) if __name__ == "__main__": conf = { 'port': 'auto', 'baud': 500000, - 'force_speed' : 0, + 'force_speed': 0, 'address': None, 'force': 0, 'erase': 0, 'write': 0, + 'erase_write': 0, + 'erase_page': 0, 'verify': 0, 'read': 0, 'len': 0x80000, - 'fname':'', + 'fname': '', 'ieee_address': 0, 'bootloader_active_high': False, - 'bootloader_invert_lines' : False, + 'bootloader_invert_lines': False, 'disable-bootloader': 0 } # http://www.python.org/doc/2.5.2/lib/module-getopt.html try: - opts, args = getopt.getopt(sys.argv[1:], "DhqVfewvrp:b:a:l:i:", ['help', 'ieee-address=', 'disable-bootloader', 'bootloader-active-high', 'bootloader-invert-lines', 'version']) + opts, args = getopt.getopt(sys.argv[1:], + "DhqVfeE:wWvrp:b:a:l:i:", + ['help', 'ieee-address=','erase-write=', + 'erase-page=', + 'disable-bootloader', + 'bootloader-active-high', + 'bootloader-invert-lines', 'version']) except getopt.GetoptError as err: # print help information and exit: - print(str(err)) # will print something like "option -a not recognized" + print(str(err)) # will print something like "option -a not recognized" usage() sys.exit(2) @@ -959,6 +1106,10 @@ def usage(): conf['erase'] = 1 elif o == '-w': conf['write'] = 1 + elif o == '-W' or o == '--erase-write': + conf['erase_write'] = 1 + elif o == '-E' or o == '--erase-page': + conf['erase_page'] = str(a) elif o == '-v': conf['verify'] = 1 elif o == '-r': @@ -988,22 +1139,28 @@ def usage(): try: # Sanity checks - if conf['write'] or conf['read'] or conf['verify']: # check for input/output file + # check for input/output file + if conf['write'] or conf['erase_write'] or conf['read'] or conf['verify']: try: args[0] except: raise Exception('No file path given.') - if conf['write'] and conf['read']: - if not ( conf['force'] or query_yes_no("You are reading and writing to the same file. This will overwrite your input file. "\ - "Do you want to continue?","no") ): + if (conf['write'] and conf['read']) or (conf['erase_write'] and conf['read']): + if not (conf['force'] or + query_yes_no("You are reading and writing to the same " + "file. This will overwrite your input file. " + "Do you want to continue?", "no")): raise Exception('Aborted by user.') - if conf['erase'] and conf['read'] and not conf['write']: - if not ( conf['force'] or query_yes_no("You are about to erase your target before reading. "\ - "Do you want to continue?","no") ): + if ((conf['erase'] and conf['read']) or (conf['erase_page'] and conf['read']) + and not (conf['write'] or conf['erase_write'])): + if not (conf['force'] or + query_yes_no("You are about to erase your target before " + "reading. Do you want to continue?", "no")): raise Exception('Aborted by user.') - if conf['read'] and not conf['write'] and conf['verify']: + if (conf['read'] and not (conf['write'] or conf['erase_write']) + and conf['verify']): raise Exception('Verify after read not implemented.') if conf['len'] < 0: @@ -1015,7 +1172,11 @@ def usage(): ports = [] # Get a list of all USB-like names in /dev - for name in ['tty.usbserial', 'ttyUSB', 'tty.usbmodem', 'tty.SLAB_USBtoUART']: + for name in ['ttyACM', + 'tty.usbserial', + 'ttyUSB', + 'tty.usbmodem', + 'tty.SLAB_USBtoUART']: ports.extend(glob.glob('/dev/%s*' % name)) ports = sorted(ports) @@ -1028,20 +1189,23 @@ def usage(): cmd = CommandInterface() cmd.open(conf['port'], conf['baud']) - cmd.invoke_bootloader(conf['bootloader_active_high'], conf['bootloader_invert_lines']) - mdebug(5, "Opening port %(port)s, baud %(baud)d" % {'port':conf['port'], - 'baud':conf['baud']}) - if conf['write'] or conf['verify']: + cmd.invoke_bootloader(conf['bootloader_active_high'], + conf['bootloader_invert_lines']) + mdebug(5, "Opening port %(port)s, baud %(baud)d" + % {'port': conf['port'], 'baud': conf['baud']}) + if conf['write'] or conf['erase_write'] or conf['verify']: mdebug(5, "Reading data from %s" % args[0]) firmware = FirmwareFile(args[0]) mdebug(5, "Connecting to target...") if not cmd.sendSynch(): - raise CmdException("Can't connect to target. Ensure boot loader is started. (no answer on synch sequence)") + raise CmdException("Can't connect to target. Ensure boot loader " + "is started. (no answer on synch sequence)") # if (cmd.cmdPing() != 1): - # raise CmdException("Can't connect to target. Ensure boot loader is started. (no answer on ping command)") + # raise CmdException("Can't connect to target. Ensure boot loader " + # "is started. (no answer on ping command)") chip_id = cmd.cmdGetChipId() chip_id_str = CHIP_ID_STRS.get(chip_id, None) @@ -1058,54 +1222,81 @@ def usage(): conf['address'] = device.flash_start_addr if conf['force_speed'] != 1 and device.has_cmd_set_xosc: - if cmd.cmdSetXOsc(): #switch to external clock source + if cmd.cmdSetXOsc(): # switch to external clock source cmd.close() conf['baud'] = 1000000 cmd.open(conf['port'], conf['baud']) - mdebug(6, "Opening port %(port)s, baud %(baud)d" % {'port':conf['port'], 'baud':conf['baud']}) + mdebug(6, "Opening port %(port)s, baud %(baud)d" + % {'port': conf['port'], 'baud': conf['baud']}) mdebug(6, "Reconnecting to target at higher speed...") if (cmd.sendSynch() != 1): - raise CmdException("Can't connect to target after clock source switch. (Check external crystal)") + raise CmdException("Can't connect to target after clock " + "source switch. (Check external " + "crystal)") else: - raise CmdException("Can't switch target to external clock source. (Try forcing speed)") + raise CmdException("Can't switch target to external clock " + "source. (Try forcing speed)") if conf['erase']: - # we only do full erase for now + mdebug(5, " Performing mass erase") if device.erase(): mdebug(5, " Erase done") else: raise CmdException("Erase failed") + if conf['erase_page']: + erase_range = parse_page_address_range(device, conf['erase_page']) + mdebug(5, "Erasing %d bytes at addres 0x%x" + % (erase_range[1], erase_range[0])) + cmd.cmdEraseMemory(erase_range[0], erase_range[1]) + mdebug(5, " Partial erase done ") + if conf['write']: - # TODO: check if boot loader back-door is open, need to read flash size first to get address + # TODO: check if boot loader back-door is open, need to read + # flash size first to get address if cmd.writeMemory(conf['address'], firmware.bytes): mdebug(5, " Write done ") else: raise CmdException("Write failed ") + if conf['erase_write']: + # TODO: check if boot loader back-door is open, need to read + # flash size first to get address + # Round up to ensure page alignment + erase_len = device.page_align_up(len(firmware.bytes)) + erase_len = min(erase_len, device.size) + if cmd.cmdEraseMemory(conf['address'], erase_len): + mdebug(5, " Erase before write done ") + if cmd.writeMemory(conf['address'], firmware.bytes): + mdebug(5, " Write done ") + else: + raise CmdException("Write failed ") + if conf['verify']: - mdebug(5,"Verifying by comparing CRC32 calculations.") + mdebug(5, "Verifying by comparing CRC32 calculations.") crc_local = firmware.crc32() - crc_target = device.crc(conf['address'], len(firmware.bytes)) #CRC of target will change according to length input file + # CRC of target will change according to length input file + crc_target = device.crc(conf['address'], len(firmware.bytes)) if crc_local == crc_target: mdebug(5, " Verified (match: 0x%08x)" % crc_local) else: cmd.cmdReset() - raise Exception("NO CRC32 match: Local = 0x%x, Target = 0x%x" % (crc_local,crc_target)) + raise Exception("NO CRC32 match: Local = 0x%x, " + "Target = 0x%x" % (crc_local, crc_target)) if conf['ieee_address'] != 0: ieee_addr = parse_ieee_address(conf['ieee_address']) - if PY3: - mdebug(5, "Setting IEEE address to %s" % (':'.join(['%02x' % b for b in struct.pack('>Q', ieee_addr)]))) - ieee_addr_bytes = struct.pack('Q', ieee_addr)]))) - ieee_addr_bytes = [ord(b) for b in struct.pack('Q', ieee_addr)]))) + ieee_addr_bytes = struct.pack('> 2): - rdata = device.read_memory(conf['address'] + (i * 4)) #reading 4 bytes at a time - mdebug(5, " 0x%x: 0x%02x%02x%02x%02x" % (conf['address'] + (i * 4), rdata[0], rdata[1], rdata[2], rdata[3]), '\r') + # reading 4 bytes at a time + rdata = device.read_memory(conf['address'] + (i * 4)) + mdebug(5, " 0x%x: 0x%02x%02x%02x%02x" + % (conf['address'] + (i * 4), rdata[0], rdata[1], + rdata[2], rdata[3]), '\r') f.write(rdata) f.close() mdebug(5, " Read done ") diff --git a/otbox.py b/otbox.py index af63a9b..9ebf625 100644 --- a/otbox.py +++ b/otbox.py @@ -19,7 +19,7 @@ import argparse import logging -try: +try: from PIL import Image from PIL import ImageFont from PIL import ImageDraw @@ -46,6 +46,10 @@ IMAGE_THREAD_NAME = 'image_thread' HEARTBEAT_THREAD_NAME = 'heartbeat_thread' + +OPENMOTE_B_FLASHSIZE = 512*1024 +CC2538_FLASHPAGE_SIZE = 2048 + #============================ classes ========================================= def _getThreadsName(): @@ -87,35 +91,54 @@ def __init__(self, otbox): self.firmware_temp = os.path.join(os.path.dirname(__file__), 'bootloaders', 'opentestbed', 'firmware_mote_{0}.ihex') def bootload_mote(self, serialport, firmware_file): - + bootloader_backdoor_enabled = False extended_linear_address_found = False - - # make sure bootloader backdoor is configured correctly - with open(firmware_file,'r') as f: - for line in f: - - # looking for data at address 0027FFD4 - # refer to: https://en.wikipedia.org/wiki/Intel_HEX#Record_types - - # looking for upper 16bit address 0027 - if len(line)>=15 and line[:15] == ':020000040027D3': - extended_linear_address_found = True - - # check the lower 16bit address FFD4 - - # | 1:3 byte count | 3:7 address | 9:17 32-bit field of the lock bit page (the last byte is backdoor configuration) | - # 'F6' = 111 1 0 110 - # reserved backdoor and bootloader enable active low PA pin used for backdoor enabling (PA6) - if len(line)>=17 and extended_linear_address_found and line[3:7] == 'FFD4' and int(line[1:3], 16)>4 and line[9:17] == 'FFFFFFF6': - bootloader_backdoor_enabled = True - break - + + # When building RIOT with OpenWSN-fw + SUIT the Customer + # Configuration Area (CCA) is not touched. The Customer + # CCA holds the Bootloader Backdoor Configuration, + # Application Entry Point, flashpage lock bits. + # When using SUIT + cc2538 RIOT does not touch this region so + # that the entry point is not changed when updating the device + # with new firmware (the entry point must allways be riot's + # bootloader). + # The CCA field resides in the last flashpage, for cc2538 + # each flashpage is 2048 bytes. Only openmote-b are present + # in the testbed and the flashsize is allways 512Kb. Since + # flashing at an offset is not supported only check that the + # target firmware does not override the CCA region. + if '.bin' in firmware_file: + if os.path.getsize(firmware_file) < (OPENMOTE_B_FLASHSIZE) - CC2538_FLASHPAGE_SIZE: + bootloader_backdoor_enabled = True + else: + # make sure bootloader backdoor is configured correctly + with open(firmware_file,'r') as f: + for line in f: + + # looking for data at address 0027FFD4 + # refer to: https://en.wikipedia.org/wiki/Intel_HEX#Record_types + + # looking for upper 16bit address 0027 + if len(line)>=15 and line[:15] == ':020000040027D3': + extended_linear_address_found = True + + # check the lower 16bit address FFD4 + + # | 1:3 byte count | 3:7 address | 9:17 32-bit field of the lock bit page (the last byte is backdoor configuration) | + # 'F6' = 111 1 0 110 + # reserved backdoor and bootloader enable active low PA pin used for backdoor enabling (PA6) + if len(line)>=17 and extended_linear_address_found and line[3:7] == 'FFD4' and int(line[1:3], 16)>4 and line[9:17] == 'FFFFFFF6': + bootloader_backdoor_enabled = True + break + assert bootloader_backdoor_enabled - + return subprocess.Popen( - ['python', 'bootloaders/cc2538-bsl.py', '-e', '--bootloader-invert-lines', '-w', '-b', '400000', - '-p', serialport, firmware_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ['python3', 'bootloaders/cc2538-bsl.py', + '--bootloader-invert-lines', '--erase-write', + '-v', '-b', '400000', '-p', + serialport, firmware_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE) def _display_image(self): import pygame @@ -317,7 +340,7 @@ def _on_mqtt_connect(self, client, userdata, flags, rc): self.mqttconnected = True # subscribe to box commands (device-specific and 'all') - # note that unknown commands will be ignored by _execute_command_safely + # note that unknown commands will be ignored by _execute_command_safely client.subscribe('{0}/#'.format(self.mqtttopic_box_cmd_prefix)) client.subscribe('{0}/deviceType/box/deviceId/all/cmd/#'.format(self.testbed)) @@ -572,11 +595,11 @@ def _mqtt_handler_program(self, deviceType, deviceId, payload): payload = json.loads(payload) # shorthand mote = self._eui64_to_moteinfoelem(deviceId) - + # disconnect from the serialports self.SerialportHandlers[mote['serialport']].disconnectSerialPort() time.sleep(2) # wait 2 seconds to release the serial ports - + if 'url' in payload and payload['url'].startswith("ftp://"): # use urllib to get firmware from ftp server (requests doesn't support for ftp) urllib.urlretrieve(payload['url'],self.tb.firmware_temp.format(deviceId)) @@ -679,14 +702,14 @@ def _bootload_motes(self, serialports, firmware_file): ''' bootloads firmware_file onto multiple motes in parallel ''' - + # start bootloading each mote ongoing_bootloads = {} for serialport in serialports: - + # simply the name port = serialport.split('/')[-1] - + # stop serial reader ongoing_bootloads[port] = self.tb.bootload_mote(serialport, firmware_file) @@ -694,7 +717,7 @@ def _bootload_motes(self, serialports, firmware_file): for ongoing_bootload in ongoing_bootloads: # wait for this bootload process to finish (stdout, stderr) = ongoing_bootloads[ongoing_bootload].communicate() - + # record the last output of bootload process with open("log_{0}.txt".format(ongoing_bootload),'w') as f: f.write("stdout: {0} stderr {1}".format(stdout,stderr))