summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: cf8496c)
raw | patch | inline | side by side (parent: cf8496c)
author | Borja Martinez <borja.martinez@gmail.com> | |
Sun, 25 Sep 2016 17:01:00 +0000 (19:01 +0200) | ||
committer | Borja Martinez <borja.martinez@gmail.com> | |
Sun, 25 Sep 2016 17:01:00 +0000 (19:01 +0200) |
Basic-Test-Package/BSL/CC2650/cc2650-bsl.py | [new file with mode: 0644] | patch | blob |
diff --git a/Basic-Test-Package/BSL/CC2650/cc2650-bsl.py b/Basic-Test-Package/BSL/CC2650/cc2650-bsl.py
--- /dev/null
@@ -0,0 +1,1167 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014, Jelmer Tiete <jelmer@tiete.be>.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote
+# products derived from this software without specific prior
+# written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Implementation based on stm32loader by Ivan A-R <ivan@tuxotronic.org>
+
+# Serial boot loader over UART for CC13xx / CC2538 / CC26xx
+# Based on the info found in TI's swru333a.pdf (spma029.pdf)
+#
+# Bootloader only starts if no valid image is found or if boot loader
+# backdoor is enabled.
+# 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 glob
+import time
+import tempfile
+import os
+import subprocess
+import struct
+import binascii
+import traceback
+
+try:
+ import magic
+ have_magic = True
+except ImportError:
+ have_magic = False
+
+try:
+ from intelhex import IntelHex
+ have_hex_support = True
+except ImportError:
+ have_hex_support = False
+
+#version
+VERSION_STRING = "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('')
+ 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')
+ sys.exit(1)
+
+
+def mdebug(level, message, attr='\n'):
+ if QUIET >= level:
+ print(message, end=attr, file=sys.stderr)
+
+# Takes chip IDs (obtained via Get ID command) to human-readable names
+CHIP_ID_STRS = {0xb964: 'CC2538'}
+
+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
+COMMAND_RET_INVALID_CMD = 0x42
+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
+ """
+ self._crc32 = None
+ firmware_is_hex = False
+
+ if have_magic:
+ file_type = bytearray(magic.from_file(path, True))
+
+ #from_file() returns bytes with PY3, str with PY2. This comparison
+ #will be True in both cases"""
+ if file_type == b'text/plain':
+ firmware_is_hex = True
+ mdebug(5, "Firmware file: Intel Hex")
+ elif file_type == b'application/octet-stream':
+ mdebug(5, "Firmware file: Raw Binary")
+ else:
+ error_str = "Could not determine firmware type. Magic " \
+ "indicates '%s'" % (file_type)
+ raise CmdException(error_str)
+ else:
+ if os.path.splitext(path)[1][1:] in self.HEX_FILE_EXTENSIONS:
+ firmware_is_hex = True
+ mdebug(5, "Your firmware looks like an Intel Hex file")
+ else:
+ mdebug(5, "Cannot auto-detect firmware filetype: Assuming .bin")
+
+ mdebug(10, "For more solid firmware type auto-detection, install "
+ "python-magic.")
+ mdebug(10, "Please see the readme for more details.")
+
+ if firmware_is_hex:
+ if have_hex_support:
+ self.bytes = bytearray(IntelHex(path).tobinarray())
+ return
+ else:
+ error_str = "Firmware is Intel Hex, but the IntelHex library " \
+ "could not be imported.\n" \
+ "Install IntelHex in site-packages or program " \
+ "your device with a raw binary (.bin) file.\n" \
+ "Please see the readme for more details."
+ raise CmdException(error_str)
+
+ with open(path, 'rb') as f:
+ self.bytes = bytearray(f.read())
+
+ 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
+ """
+ if self._crc32 is None:
+ self._crc32 = binascii.crc32(bytearray(self.bytes)) & 0xffffffff
+
+ 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=3 # set a timeout value, None for waiting forever
+ )
+
+
+ def invoke_bootloader(self, dtr_active_high=False, inverted=False):
+ # Use the DTR and RTS lines to control bootloader and the !RESET pin.
+ # This can automatically invoke the bootloader without the user
+ # having to toggle any pins.
+ #
+ # If inverted is False (default):
+ # DTR: connected to the bootloader pin
+ # RTS: connected to !RESET
+ # If inverted is True, pin connections are the other way round
+ if inverted:
+ set_bootloader_pin = self.sp.setRTS
+ set_reset_pin = self.sp.setDTR
+ else:
+ set_bootloader_pin = self.sp.setDTR
+ set_reset_pin = self.sp.setRTS
+
+ # BMH: Forze DTR
+ mdebug(0, "Forze DTR Inactive (Active = %d) RTS-DTR Inverted: %d" % (dtr_active_high,inverted))
+ set_bootloader_pin(1 if dtr_active_high else 0)
+ time.sleep(0.2)
+
+ set_bootloader_pin(0 if dtr_active_high else 1)
+ 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.
+ set_bootloader_pin(0 if not dtr_active_high else 1)
+
+ #set_reset_pin(0)
+ #set_bootloader_pin(0)
+ #set_reset_pin(1)
+ #set_bootloader_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.
+ #set_bootloader_pin(0 if not dtr_active_high else 1)
+
+
+
+
+ # Some boards have a co-processor that detects this sequence here and
+ # then drives the main chip's BSL enable and !RESET pins. Depending on
+ # board design and co-processor behaviour, the !RESET pin may get
+ # asserted after we have finished the sequence here. In this case, we
+ # need a small delay so as to avoid trying to talk to main chip before
+ # it has actually entered its bootloader mode.
+ #
+ # See contiki-os/contiki#1533
+ time.sleep(0.1)
+
+ def close(self):
+ self.sp.close()
+
+
+ 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,
+ CommandInterface.NACK_BYTE):
+ got += self._read(1)
+ if time.time() > stop:
+ raise CmdException("Timeout waiting for ACK/NACK after '%s'"
+ % (info,))
+
+ # Our bytearray's length is: 2 initial bytes + 2 bytes for the ACK/NACK
+ # plus a possible N-4 additional (buffered) bytes
+ mdebug(10, "Got %d additional bytes before ACK/NACK" % (len(got) - 4,))
+
+ # wait for ask
+ ask = got[-1]
+
+ if ask == CommandInterface.ACK_BYTE:
+ # ACK
+ return 1
+ elif ask == CommandInterface.NACK_BYTE:
+ # NACK
+ mdebug(10, "Target replied with a NACK during %s" % info)
+ return 0
+
+ # Unknown response
+ mdebug(10, "Unrecognised response 0x%x to %s" % (ask, info))
+ return 0
+
+ def _encode_addr(self, addr):
+ byte3 = (addr >> 0) & 0xFF
+ 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))
+
+ 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)
+
+ 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)))
+ 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)
+ if 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")
+ if type(data) == int:
+ return self._write(data, is_retry=True)
+ else:
+ return self._write(data[written:], is_retry=True)
+
+ def _read(self, length):
+ return bytearray(self.sp.read(length))
+
+ def sendAck(self):
+ self._write(0x00)
+ self._write(0xCC)
+ return
+
+ def sendNAck(self):
+ self._write(0x00)
+ self._write(0x33)
+ return
+
+
+ def receivePacket(self):
+ # stop = time.time() + 5
+ # got = None
+ # while not got:
+ got = self._read(2)
+ # if time.time() > stop:
+ # break
+
+ # 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
+
+ mdebug(10, "*** received %x bytes" % size)
+ if chks == sum(data)&0xFF:
+ self.sendAck()
+ return data
+ else:
+ self.sendNAck()
+ #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
+
+ mdebug(10, "*** sending synch sequence")
+ 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?)")
+
+ if stat[0] == COMMAND_RET_SUCCESS:
+ mdebug(10, "Command Successful")
+ return 1
+ 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])
+ 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
+
+ mdebug(10, "*** Ping command (0x20)")
+ if self._wait_for_ack("Ping (0x20)"):
+ return self.checkLastCmd()
+
+ def cmdReset(self):
+ cmd = 0x25
+ lng = 3
+
+ 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)"):
+ return 1
+
+ def cmdGetChipId(self):
+ cmd = 0x28
+ lng = 3
+
+ 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
+ if self.checkLastCmd():
+ 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
+ else:
+ raise CmdException("GetChipID (0x28) failed")
+
+ def cmdGetStatus(self):
+ cmd = 0x23
+ lng = 3
+
+ 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)"):
+ stat = self.receivePacket()
+ return stat
+
+ def cmdSetXOsc(self):
+ cmd = 0x29
+ lng = 3
+
+ 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)"):
+ return 1
+ # UART speed (needs) to be changed!
+
+ def cmdRun(self, addr):
+ 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
+
+ mdebug(10, "*** Run command(0x22)")
+ return 1
+
+ def cmdEraseMemory(self, addr, size):
+ 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
+
+ mdebug(10, "*** Erase command(0x26)")
+ 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
+
+ mdebug(10, "*** Bank Erase command(0x2C)")
+ if self._wait_for_ack("Bank Erase (0x2C)",10):
+ return self.checkLastCmd()
+
+ def cmdCRC32(self, addr, size):
+ 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
+
+ mdebug(10, "*** CRC32 command(0x27)")
+ 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])
+
+ 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
+
+ mdebug(10, "*** CRC32 command(0x27)")
+ 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])
+
+ def cmdDownload(self, addr, size):
+ 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)
+
+ 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):
+ return self.checkLastCmd()
+
+ def cmdSendData(self, data):
+ 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
+
+ mdebug(10, "*** Send Data (0x24)")
+ if self._wait_for_ack("Send data (0x24)",10):
+ return self.checkLastCmd()
+
+ 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
+
+ mdebug(10, "*** Mem Read (0x2A)")
+ 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]))
+
+ 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
+
+ mdebug(10, "*** Mem Read (0x2A)")
+ if self._wait_for_ack("Mem Read (0x2A)", 1):
+ data = self.receivePacket()
+ 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
+
+ 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
+
+ mdebug(10, "*** Mem write (0x2B)")
+ if self._wait_for_ack("Mem Write (0x2B)",2):
+ return 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)
+ 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") ):
+ raise Exception('Aborted by user.')
+
+ mdebug(5, "Writing %(lng)d bytes starting at address 0x%(addr)08X" %
+ { '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
+ if addr_set != 1:
+ self.cmdDownload(addr,lng) #set starting address if not set
+ addr_set = 1
+ 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
+ else: # skipped packet, address needs to be set
+ addr_set = 0
+
+ offs = offs + trsf_size
+ 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
+
+class Chip(object):
+ def __init__(self, command_interface):
+ self.command_interface = command_interface
+
+ # Some defaults. The child can override.
+ self.flash_start_addr = 0x00000000
+ self.has_cmd_set_xosc = False
+
+ 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")):
+ raise Exception('Aborted by user.')
+
+ if PY3:
+ pattern = struct.pack('<L', self.bootloader_dis_val)
+ else:
+ pattern = [ord(b) for b in struct.pack('<L', self.bootloader_dis_val)]
+
+ if cmd.writeMemory(self.bootloader_address, pattern):
+ mdebug(5, " Set bootloader closed done ")
+ else:
+ raise CmdException("Set bootloader closed failed ")
+
+class CC2538(Chip):
+ def __init__(self, command_interface):
+ super(CC2538, self).__init__(command_interface)
+ self.flash_start_addr = 0x00200000
+ self.addr_ieee_address_secondary = 0x0027ffcc
+ self.has_cmd_set_xosc = True
+ self.bootloader_dis_val = 0xefffffff
+ self.crc_cmd = "cmdCRC32"
+
+ FLASH_CTRL_DIECFG0 = 0x400D3014
+ FLASH_CTRL_DIECFG2 = 0x400D301C
+ addr_ieee_address_primary = 0x00280028
+ ccfg_len = 44
+
+ #Read out primary IEEE address, flash and RAM size
+ model = self.command_interface.cmdMemRead(FLASH_CTRL_DIECFG0)
+ self.size = (model[3] & 0x70) >> 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 = (((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
+
+ 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)))
+
+ 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)
+
+ def read_memory(self, addr):
+ # CC2538's COMMAND_MEMORY_READ sends each 4-byte number in inverted
+ # byte order compared to what's written on the device
+ 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
+ PROTO_MASK_BLE = 0x01
+ PROTO_MASK_IEEE = 0x04
+ PROTO_MASK_BOTH = 0x05
+
+ def __init__(self, command_interface):
+ super(CC26xx, self).__init__(command_interface)
+ self.bootloader_dis_val = 0x00000000
+ self.crc_cmd = "cmdCRC32CC26xx"
+
+ ICEPICK_DEVICE_ID = 0x50001318
+ FCFG_USER_ID = 0x50001294
+ PRCM_RAMHWOPT = 0x40082250
+ FLASH_SIZE = 0x4003002C
+ addr_ieee_address_primary = 0x500012F0
+ ccfg_len = 88
+ ieee_address_secondary_offset = 0x20
+ bootloader_dis_offset = 0x30
+ sram = "Unknown"
+
+ # Determine CC13xx vs CC26xx via ICEPICK_DEVICE_ID::WAFER_ID and store
+ # PG revision
+ device_id = self.command_interface.cmdMemReadCC26xx(ICEPICK_DEVICE_ID)
+ wafer_id = (((device_id[3] & 0x0F) << 16) +
+ (device_id[2] << 8) +
+ (device_id[1] & 0xF0)) >> 4
+ pg_rev = (device_id[3] & 0xF0) >> 4
+
+ # 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")
+ protocols = user_id[1] >> 4
+
+ # We can now detect the exact device
+ if wafer_id == 0xB99A:
+ chip = self._identify_cc26xx(pg_rev, protocols)
+ elif wafer_id == 0xB9BE:
+ chip = self._identify_cc13xx(pg_rev, protocols)
+
+ # Read flash size, calculate and store bootloader disable address
+ self.size = self.command_interface.cmdMemReadCC26xx(FLASH_SIZE)[0] * 4096
+ self.bootloader_address = self.size - ccfg_len + bootloader_dis_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
+ if ramhwopt_size == 3:
+ sram = "20KB"
+ elif ramhwopt_size == 2:
+ sram = "16KB"
+ else:
+ 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]
+
+ 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)))
+
+ def _identify_cc26xx(self, pg, protocols):
+ chips_dict = {
+ CC26xx.PROTO_MASK_IEEE: 'CC2630',
+ CC26xx.PROTO_MASK_BLE: 'CC2640',
+ CC26xx.PROTO_MASK_BOTH: 'CC2650',
+ }
+
+ chip_str = chips_dict.get(protocols & CC26xx.PROTO_MASK_BOTH, "Unknown")
+
+ if pg == 1:
+ pg_str = "PG1.0"
+ elif pg == 3:
+ 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]
+ if rev_minor == 0xFF:
+ rev_minor = 0x00
+ pg_str = "PG2.%d" % (2 + rev_minor,)
+
+ return "%s %s" % (chip_str, pg_str)
+
+ def _identify_cc13xx(self, pg, protocols):
+ chip_str = "CC1310"
+ if protocols & CC26xx.PROTO_MASK_IEEE == CC26xx.PROTO_MASK_IEEE:
+ chip_str = "CC1350"
+
+ if pg == 0:
+ pg_str = "PG1.0"
+ elif pg == 2:
+ rev_minor = self.command_interface.cmdMemReadCC26xx(CC26xx.MISC_CONF_1)[0]
+ if rev_minor == 0xFF:
+ rev_minor = 0x00
+ pg_str = "PG2.%d" % (rev_minor,)
+
+ return "%s %s" % (chip_str, pg_str)
+
+ def erase(self):
+ mdebug(5, "Erasing all main bank flash sectors")
+ return self.command_interface.cmdBankErase()
+
+ def read_memory(self, addr):
+ # CC26xx COMMAND_MEMORY_READ returns contents in the same order as
+ # 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:
+ prompt = " [y/n] "
+ elif default == "yes":
+ prompt = " [Y/n] "
+ elif default == "no":
+ prompt = " [y/N] "
+ else:
+ raise ValueError("invalid default answer: '%s'" % default)
+
+ while True:
+ sys.stdout.write(question + prompt)
+ if PY3:
+ choice = input().lower()
+ else:
+ choice = raw_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' "\
+ "(or 'y' or 'n').\n")
+
+# Convert the entered IEEE address into an integer
+def parse_ieee_address (inaddr):
+ try:
+ return int(inaddr, 16)
+ except ValueError:
+ # inaddr is not a hex string, look for other formats
+ if ':' in inaddr:
+ bytes = inaddr.split(':')
+ elif '-' in inaddr:
+ bytes = inaddr.split('-')
+ if len(bytes) != 8:
+ raise ValueError("Supplied IEEE address does not contain 8 bytes")
+ addr = 0
+ 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 print_version():
+ # Get the version using "git describe".
+ try:
+ p = Popen(['git', 'describe', '--tags', '--match', '[0-9]*'],
+ stdout=PIPE, stderr=PIPE)
+ p.stderr.close()
+ line = p.stdout.readlines()[0]
+ version = line.strip()
+ except:
+ # We're not in a git repo, or git failed, use fixed version string.
+ version = VERSION_STRING
+ 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
+
+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]))
+
+if __name__ == "__main__":
+
+ conf = {
+ 'port': 'auto',
+ 'baud': 500000,
+ 'force_speed' : 0,
+ 'address': None,
+ 'force': 0,
+ 'erase': 0,
+ 'write': 0,
+ 'verify': 0,
+ 'read': 0,
+ 'len': 0x80000,
+ 'fname':'',
+ 'ieee_address': 0,
+ 'bootloader_active_high': 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'])
+ except getopt.GetoptError as err:
+ # print help information and exit:
+ print(str(err)) # will print something like "option -a not recognized"
+ usage()
+ sys.exit(2)
+
+ for o, a in opts:
+ if o == '-V':
+ QUIET = 10
+ elif o == '-q':
+ QUIET = 0
+ elif o == '-h' or o == '--help':
+ usage()
+ sys.exit(0)
+ elif o == '-f':
+ conf['force'] = 1
+ elif o == '-e':
+ conf['erase'] = 1
+ elif o == '-w':
+ conf['write'] = 1
+ elif o == '-v':
+ conf['verify'] = 1
+ elif o == '-r':
+ conf['read'] = 1
+ elif o == '-p':
+ conf['port'] = a
+ elif o == '-b':
+ conf['baud'] = eval(a)
+ conf['force_speed'] = 1
+ elif o == '-a':
+ conf['address'] = eval(a)
+ elif o == '-l':
+ conf['len'] = eval(a)
+ elif o == '-i' or o == '--ieee-address':
+ conf['ieee_address'] = str(a)
+ elif o == '--bootloader-active-high':
+ conf['bootloader_active_high'] = True
+ elif o == '--bootloader-invert-lines':
+ conf['bootloader_invert_lines'] = True
+ elif o == '-D' or o == '--disable-bootloader':
+ conf['disable-bootloader'] = 1
+ elif o == '--version':
+ print_version()
+ sys.exit(0)
+ else:
+ assert False, "Unhandled option"
+
+ try:
+ # Sanity checks
+ if conf['write'] or conf['read'] or conf['verify']: # check for input/output file
+ 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") ):
+ 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") ):
+ raise Exception('Aborted by user.')
+
+ if conf['read'] and not conf['write'] and conf['verify']:
+ raise Exception('Verify after read not implemented.')
+
+ if conf['len'] < 0:
+ raise Exception('Length must be positive but %d was provided'
+ % (conf['len'],))
+
+ # Try and find the port automatically
+ if conf['port'] == 'auto':
+ ports = []
+
+ # Get a list of all USB-like names in /dev
+ for name in ['tty.usbserial', 'ttyUSB', 'tty.usbmodem', 'tty.SLAB_USBtoUART']:
+ ports.extend(glob.glob('/dev/%s*' % name))
+
+ ports = sorted(ports)
+
+ if ports:
+ # Found something - take it
+ conf['port'] = ports[0]
+ else:
+ raise Exception('No serial port found.')
+
+ 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']:
+ 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)")
+
+ # if (cmd.cmdPing() != 1):
+ # 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)
+
+ if chip_id_str is None:
+ mdebug(10, ' Unrecognized chip ID. Trying CC13xx/CC26xx')
+ device = CC26xx(cmd)
+ else:
+ mdebug(10, " Target id 0x%x, %s" % (chip_id, chip_id_str))
+ device = CC2538(cmd)
+
+ # Choose a good default address unless the user specified -a
+ if conf['address'] is None:
+ 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
+ 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, "Reconnecting to target at higher speed...")
+ if (cmd.sendSynch() != 1):
+ 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)")
+
+ if conf['erase']:
+ # we only do full erase for now
+ if device.erase():
+ mdebug(5, " Erase done")
+ else:
+ raise CmdException("Erase failed")
+
+ if conf['write']:
+ # 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['verify']:
+ 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
+
+ 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))
+
+ 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)
+ else:
+ mdebug(5, "Setting IEEE address to %s" % (':'.join(['%02x' % ord(b) for b in struct.pack('>Q', ieee_addr)])))
+ ieee_addr_bytes = [ord(b) for b in struct.pack('<Q', ieee_addr)]
+
+ if cmd.writeMemory(device.addr_ieee_address_secondary, ieee_addr_bytes):
+ mdebug(5, " Set address done ")
+ else:
+ raise CmdException("Set address failed ")
+
+ if conf['read']:
+ length = conf['len']
+
+ # Round up to a 4-byte boundary
+ length = (length + 3) & ~0x03
+
+ mdebug(5, "Reading %s bytes starting at address 0x%x" % (length, conf['address']))
+ with open(args[0], 'wb') as f:
+ for i in range(0, length >> 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')
+ f.write(rdata)
+ f.close()
+ mdebug(5, " Read done ")
+
+ if conf['disable-bootloader']:
+ device.disable_bootloader()
+
+ cmd.cmdReset()
+
+ except Exception as err:
+ if QUIET >= 10:
+ traceback.print_exc()
+ exit('ERROR: %s' % str(err))