├── .gitignore ├── .travis.yml ├── pyproject.toml ├── tests └── test_cc2538-bsl.py ├── setup.py ├── LICENSE.md ├── README.md └── cc2538-bsl.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Unit test / coverage reports 10 | test-output/ 11 | htmlcov/ 12 | .tox/ 13 | .coverage 14 | .coverage.* 15 | .cache 16 | nosetests.xml 17 | coverage.xml 18 | *,cover 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | 9 | # command to install dependencies 10 | install: 11 | - "pip install pyserial" 12 | - "pip install scripttest" 13 | 14 | # command to run tests 15 | script: nosetests -v ./tests/test_cc2538-bsl.py 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=60", 4 | "setuptools-scm>=8.0"] 5 | build-backend = "setuptools.build_meta" 6 | 7 | [project] 8 | name = "cc2538-bsl" 9 | dynamic = ["version", "description", "readme", "requires-python", "license", "authors", "keywords", "classifiers", "dependencies", "optional-dependencies"] 10 | [tool.setuptools_scm] 11 | -------------------------------------------------------------------------------- /tests/test_cc2538-bsl.py: -------------------------------------------------------------------------------- 1 | from scripttest import TestFileEnvironment 2 | import shutil 3 | 4 | # Init test environment 5 | env = TestFileEnvironment('./test-output') 6 | 7 | # Tests 8 | 9 | # Make sure there is help output 10 | def test_help_output(): 11 | res = env.run('python', './../cc2538-bsl.py', '-h', '--help') 12 | 13 | # Test for failure on no input file 14 | # TODO needs better checking 15 | def test_sanity_checks_no_input(): 16 | res = env.run('python', './../cc2538-bsl.py', '-w', '-r', '-v', expect_error=1) 17 | 18 | # Test for not implemented feature of verify after read 19 | # TODO needs better checking 20 | def test_sanity_checks_verify_after_read(): 21 | res = env.run('python', './../cc2538-bsl.py', '-r', '-v', expect_error=1) 22 | 23 | # Test for version output 24 | def test_version(): 25 | res = env.run('python', './../cc2538-bsl.py', '--version') 26 | 27 | # Clean up after tests 28 | def teardown_module(module): 29 | print ("Removing test-output folder") 30 | shutil.rmtree('./test-output') -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name="cc2538-bsl", 6 | description="Script to communicate with Texas Instruments CC13xx/CC2538/CC26xx Serial Boot Loader .", 7 | long_description=open("README.md", encoding="utf-8").read(), 8 | keywords="cc2538, cc1310, cc13xx, bootloader, cc26xx, cc2650, cc2640", 9 | url="https://github.com/JelmerT/cc2538-bsl", 10 | author="Jelmer Tiete", 11 | author_email="jelmer@tiete.be", 12 | license="BSD-3-Clause", 13 | classifiers=[ 14 | "Development Status :: 5 - Production/Stable", 15 | "Environment :: Console", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: BSD License", 18 | "Operating System :: POSIX :: Linux", 19 | "Operating System :: MacOS", 20 | "Programming Language :: Python :: 3", 21 | "Topic :: Scientific/Engineering", 22 | ], 23 | platforms="posix", 24 | python_requires=">=3.4", 25 | setup_requires=["setuptools_scm"], 26 | use_scm_version=lambda: { 27 | "version_scheme": "post-release", 28 | "local_scheme": "node-and-date", 29 | }, 30 | install_requires=["pyserial"], 31 | extras_require={ 32 | 'cc2538-bsl': ["intelhex"], 33 | 'intelhex': ["python-magic"] 34 | }, 35 | scripts=["cc2538-bsl.py"], 36 | ) 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014, Jelmer Tiete . 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TI CC13xx/CC2538/CC26xx Serial Boot Loader [![Build Status](https://travis-ci.org/JelmerT/cc2538-bsl.svg?branch=master)](https://travis-ci.org/JelmerT/cc2538-bsl) 2 | ========================================== 3 | 4 | This folder contains a python script that communicates with the boot loader of the Texas Instruments CC2538, CC26xx and CC13xx SoCs (System on Chips). 5 | It can be used to erase, program, verify and read the flash of those SoCs with a simple USB to serial converter. 6 | 7 | ### Requirements 8 | 9 | To run this script you need a Python interpreter, Linux and Mac users should be fine, Windows users have a look here: [Python Download][python]. 10 | 11 | Alternatively, Docker can be used to run this script as a one-liner without the need to install dependencies, see [git-developer/ti-cc-tool](https://github.com/git-developer/ti-cc-tool) for details. 12 | 13 | To communicate with the uart port of the SoC you need a usb to serial converter: 14 | * If you use the SmartRF06 board with an Evaluation Module (EM) mounted on it you can use the on-board ftdi chip. Make sure the "Enable UART" jumper is set on the board. You can have a look [here][contiki cc2538dk] for more info on drivers for this chip on different operating systems. 15 | * If you use a different platform, there are many cheap USB to UART converters available, but make sure you use one with 3.3v voltage levels. 16 | 17 | ### Dependencies 18 | 19 | This script uses the pyserial package to communicate with the serial port and chip (https://pypi.org/project/pyserial/). You can install it by running `pip install pyserial`. 20 | 21 | If you want to be able to program your device from an Intel Hex file, you will need to install the IntelHex package: https://pypi.python.org/pypi/IntelHex (e.g. by running `pip install intelhex`). 22 | 23 | The script will try to auto-detect whether your firmware is a raw binary or an Intel Hex by using python-magic: 24 | (https://pypi.python.org/pypi/python-magic). You can install it by running `pip install python-magic`. Please bear in mind that installation of python-magic may have additional dependencies, depending on your OS: (https://github.com/ahupp/python-magic#dependencies). 25 | 26 | If python-magic is _not_ installed, the script will try to auto-detect the firmware type by looking at the filename extension, but this is sub-optimal. If the extension is `.hex`, `.ihx` or `.ihex`, the script will assume that the firmware is an Intel Hex file. In all other cases, the firmware will be treated as raw binary. 27 | 28 | ### CC2538 29 | 30 | Once you connected the SoC you need to make sure the serial boot loader is enabled. A chip without a valid image (program), as it comes from the factory, will automatically start the boot loader. After you upload an image to the chip, the "Image Valid" bits are set to 0 to indicate that a valid image is present in flash. On the next reset the boot loader won't be started and the image is immediately executed. 31 | To make sure you don't get "locked out", i.e. not being able to communicate over serial with the boot loader in the SoC anymore, you need to enable the boot loader backdoor in your image (the script currently only checks this on firmware for the 512K model). When the boot loader backdoor is enabled the boot loader will be started when the chip is reset and a specific pin of the SoC is pulled high or low (configurable). 32 | The boot loader backdoor can be enabled and configured with the 8-bit boot loader backdoor field in the CCA area in flash. If you set this field to 0xF3FFFFFF the boot loader will be enabled when pin PA3 is pulled low during boot. This translates to holding down the `select` button on the SmartRF06 board while pushing the `EM reset` button. 33 | If you did lock yourself out or there is already an image flashed on your SoC, you will need a jtag programmer to erase the image. This will reset the image valid bits and enable the boot loader on the next reset. The SmartRF06EB contains both a jtag programmer and a USB to uart converter on board. 34 | 35 | ### CC26xx and CC13xx 36 | 37 | The script has been tested with SmartRF06EB + CC2650 EM. The physical wiring on the CC2650 Sensortag does not meet the ROM bootloader's requirements in terms of serial interface configuration. For that reason, interacting with the Sensortag via this script is (and will remain) impossible. 38 | 39 | For sensortags CC1350STK: 40 | It is possible to solder cables to R21 and R22 for flashing using the Serial Bootloader. This [issue] has instructions about flashing the CC1350STK sensortag. 41 | 42 | For ITead SONOFF Zigbee 3.0 USB Dongle Plus: 43 | For the CC2652P based "SONOFF Zigbee 3.0 USB Dongle Plus" (model "ZBDongle-P") adapter from ITead you need to invoke toggle to activate bootloader with `--bootloader-sonoff-usb` if you do not want to open its enclosure to manually start the bootloader with the boot button on the PCB. 44 | 45 | For all the CC13xx and CC26xx families, the ROM bootloader is configured through the `BL_CONFIG` 'register' in CCFG. `BOOTLOADER_ENABLE` should be set to `0xC5` to enable the bootloader in the first place. 46 | 47 | This is enough if the chip has not been programmed with a valid image. If a valid image is present, then the remaining fields of `BL_CONFIG` and the `ERASE_CONF` register must also be configured correctly: 48 | 49 | * Select a DIO by setting `BL_PIN_NUMBER` 50 | * Select an active level (low/high) for the DIO by setting `BL_LEVEL` 51 | * Enable 'failure analysis' by setting `BL_ENABLE` to `0xC5` 52 | * Make sure the `BANK_ERASE` command is enabled: The `BANK_ERASE_DIS_N` bit in the `ERASE_CONF` register in CCFG must be set. `BANK_ERASE` is enabled by default. 53 | 54 | If you are using CC13xx/CC26xxware, the relevant settings are under `startup_files/ccfg.c`. This is the case if you are using Contiki. 55 | 56 | Similar to the CC2538, the bootloader will be activated if, at the time of reset, failure analysis is enabled and the selected DIO is found to be at the active level. 57 | 58 | As an example, to bind the bootloader backdoor to KEY_SELECT on the SmartRF06EB, you need to set the following: 59 | 60 | * `BOOTLOADER_ENABLE = 0xC5` (Bootloader enable. `SET_CCFG_BL_CONFIG_BOOTLOADER_ENABLE` in CC13xx/CC26xxware) 61 | * `BL_LEVEL = 0x00` (Active low. `SET_CCFG_BL_CONFIG_BL_LEVEL` in CC13xx/CC26xxware) 62 | * `BL_PIN_NUMBER = 0x0B` (DIO 11. `SET_CCFG_BL_CONFIG_BL_PIN_NUMBER` in CC13xx/CC26xxware) 63 | * `BL_ENABLE = 0xC5` (Enable "failure analysis". `SET_CCFG_BL_CONFIG_BL_ENABLE` in CC13xx/CC26xxware) 64 | 65 | These settings are very useful for development, but enabling failure analysis in a deployed firmware may allow a malicious user to read out the contents of your device's flash or to erase it. Do not enable this in a deployment unless you understand the security implications. 66 | 67 | ### Usage 68 | 69 | The script will automatically select the first serial looking port from a USB to uart converter in `/dev` (OSX, Linux) for uploading. Be careful as on the SmartRF06B board under Linux this might be the jtag interface as apposed to the uart interface. In this case select the correct serial port manually with the `-p` option. Serial port selection under Windows needs testing. 70 | 71 | Before uploading your image make sure you start the boot loader on the SoC (`select` + `reset` on CC2538DK). 72 | You can find more info on the different options by executing `python cc2538-bsl.py -h`. 73 | 74 | ### Remarks 75 | 76 | * Programming multiple SoCs at the same time is not yet supported. 77 | * Reading the full flash of a 512K chip takes a really long time, use the length option to only read what you're interested in for now. 78 | 79 | If you found a bug or improved some part of the code, please submit an issue or pull request. 80 | 81 | ##### Authors 82 | Jelmer Tiete (c) 2014, 83 | Loosly based on [stm32loader] by Ivan A-R 84 | 85 | [![Analytics](https://ga-beacon.appspot.com/UA-3496907-10/JelmerT/cc2538-bsl?pixel)](https://github.com/igrigorik/ga-beacon) 86 | 87 | [python]: http://www.python.org/download/ "Python Download" 88 | [contiki cc2538dk]: https://github.com/contiki-os/contiki/tree/master/platform/cc2538dk "Contiki CC2538DK readme" 89 | [stm32loader]: https://github.com/jsnyder/stm32loader "stm32loader" 90 | [issue]: https://github.com/JelmerT/cc2538-bsl/issues/78 "issue" 91 | -------------------------------------------------------------------------------- /cc2538-bsl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2014, Jelmer Tiete . 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 3. The name of the author may not be used to endorse or promote 15 | # products derived from this software without specific prior 16 | # written permission. 17 | 18 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 19 | # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 24 | # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | # Implementation based on stm32loader by Ivan A-R 31 | 32 | # Serial boot loader over UART for CC13xx / CC2538 / CC26xx 33 | # Based on the info found in TI's swru333a.pdf (spma029.pdf) 34 | # 35 | # Bootloader only starts if no valid image is found or if boot loader 36 | # backdoor is enabled. 37 | # Make sure you don't lock yourself out!! (enable backdoor in your firmware) 38 | # More info at https://github.com/JelmerT/cc2538-bsl 39 | 40 | from subprocess import Popen, PIPE 41 | 42 | import sys 43 | import glob 44 | import math 45 | import time 46 | import os 47 | import struct 48 | import binascii 49 | import traceback 50 | import argparse 51 | 52 | # version 53 | __version__ = "2.1" 54 | 55 | # Verbose level 56 | QUIET = 5 57 | 58 | # Default Baud 59 | DEFAULT_BAUD = 500000 60 | 61 | try: 62 | import serial 63 | except ImportError: 64 | print('{} requires the Python serial library'.format(sys.argv[0])) 65 | print('Please install it with:') 66 | print('') 67 | print(' pip3 install pyserial') 68 | sys.exit(1) 69 | 70 | 71 | def mdebug(level, message, attr='\n'): 72 | if QUIET >= level: 73 | print(message, end=attr, file=sys.stderr) 74 | 75 | # Takes chip IDs (obtained via Get ID command) to human-readable names 76 | CHIP_ID_STRS = {0xb964: 'CC2538', 77 | 0xb965: 'CC2538' 78 | } 79 | 80 | RETURN_CMD_STRS = {0x40: 'Success', 81 | 0x41: 'Unknown command', 82 | 0x42: 'Invalid command', 83 | 0x43: 'Invalid address', 84 | 0x44: 'Flash fail' 85 | } 86 | 87 | COMMAND_RET_SUCCESS = 0x40 88 | COMMAND_RET_UNKNOWN_CMD = 0x41 89 | COMMAND_RET_INVALID_CMD = 0x42 90 | COMMAND_RET_INVALID_ADR = 0x43 91 | COMMAND_RET_FLASH_FAIL = 0x44 92 | 93 | 94 | class CmdException(Exception): 95 | pass 96 | 97 | 98 | class FirmwareFile(object): 99 | HEX_FILE_EXTENSIONS = ('hex', 'ihx', 'ihex') 100 | 101 | def __init__(self, path): 102 | """ 103 | Read a firmware file and store its data ready for device programming. 104 | 105 | This class will try to guess the file type if python-magic is available. 106 | 107 | If python-magic indicates a plain text file, and if IntelHex is 108 | available, then the file will be treated as one of Intel HEX format. 109 | 110 | In all other cases, the file will be treated as a raw binary file. 111 | 112 | In both cases, the file's contents are stored in bytes for subsequent 113 | usage to program a device or to perform a crc check. 114 | 115 | Parameters: 116 | path -- A str with the path to the firmware file. 117 | 118 | Attributes: 119 | bytes: A bytearray with firmware contents ready to send to the 120 | device 121 | """ 122 | self._crc32 = None 123 | 124 | try: 125 | from magic import from_file 126 | file_type = from_file(path, mime=True) 127 | 128 | if file_type == 'text/plain': 129 | mdebug(5, "Firmware file: Intel Hex") 130 | self.__read_hex(path) 131 | elif file_type == 'application/octet-stream': 132 | mdebug(5, "Firmware file: Raw Binary") 133 | self.__read_bin(path) 134 | else: 135 | error_str = "Could not determine firmware type. Magic " \ 136 | "indicates '%s'" % (file_type) 137 | raise CmdException(error_str) 138 | except ImportError: 139 | if os.path.splitext(path)[1][1:] in self.HEX_FILE_EXTENSIONS: 140 | mdebug(5, "Your firmware looks like an Intel Hex file") 141 | self.__read_hex(path) 142 | else: 143 | mdebug(5, "Cannot auto-detect firmware filetype: Assuming .bin") 144 | self.__read_bin(path) 145 | 146 | mdebug(10, "For more solid firmware type auto-detection, install " 147 | "python-magic.") 148 | mdebug(10, "Please see the readme for more details.") 149 | 150 | def __read_hex(self, path): 151 | try: 152 | from intelhex import IntelHex 153 | self.bytes = bytearray(IntelHex(path).tobinarray()) 154 | except ImportError: 155 | error_str = "Firmware is Intel Hex, but the IntelHex library " \ 156 | "could not be imported.\n" \ 157 | "Install IntelHex in site-packages or program " \ 158 | "your device with a raw binary (.bin) file.\n" \ 159 | "Please see the readme for more details." 160 | raise CmdException(error_str) 161 | 162 | def __read_bin(self, path): 163 | with open(path, 'rb') as f: 164 | self.bytes = bytearray(f.read()) 165 | 166 | def crc32(self): 167 | """ 168 | Return the crc32 checksum of the firmware image 169 | 170 | Return: 171 | The firmware's CRC32, ready for comparison with the CRC 172 | returned by the ROM bootloader's COMMAND_CRC32 173 | """ 174 | if self._crc32 == None: 175 | self._crc32 = binascii.crc32(bytearray(self.bytes)) & 0xffffffff 176 | 177 | return self._crc32 178 | 179 | 180 | class CommandInterface(object): 181 | 182 | ACK_BYTE = 0xCC 183 | NACK_BYTE = 0x33 184 | 185 | def open(self, aport=None, abaudrate=DEFAULT_BAUD): 186 | # Try to create the object using serial_for_url(), or fall back to the 187 | # old serial.Serial() where serial_for_url() is not supported. 188 | # serial_for_url() is a factory class and will return a different 189 | # object based on the URL. For example serial_for_url("/dev/tty.") 190 | # will return a serialposix.Serial object for Ubuntu or Mac OS; 191 | # serial_for_url("COMx") will return a serialwin32.Serial oject for Windows OS. 192 | # For that reason, we need to make sure the port doesn't get opened at 193 | # this stage: We need to set its attributes up depending on what object 194 | # we get. 195 | try: 196 | self.sp = serial.serial_for_url(aport, do_not_open=True, timeout=10, write_timeout=10) 197 | except AttributeError: 198 | self.sp = serial.Serial(port=None, timeout=10, write_timeout=10) 199 | self.sp.port = aport 200 | 201 | if ((os.name == 'nt' and isinstance(self.sp, serial.serialwin32.Serial)) or \ 202 | (os.name == 'posix' and isinstance(self.sp, serial.serialposix.Serial))): 203 | self.sp.baudrate=abaudrate # baudrate 204 | self.sp.bytesize=8 # number of databits 205 | self.sp.parity=serial.PARITY_NONE # parity 206 | self.sp.stopbits=1 # stop bits 207 | self.sp.xonxoff=0 # s/w (XON/XOFF) flow control 208 | self.sp.rtscts=0 # h/w (RTS/CTS) flow control 209 | self.sp.timeout=0.5 # set the timeout value 210 | 211 | self.sp.open() 212 | 213 | def invoke_bootloader(self, dtr_active_high=False, inverted=False, sonoff_usb=False): 214 | # Use the DTR and RTS lines to control bootloader and the !RESET pin. 215 | # This can automatically invoke the bootloader without the user 216 | # having to toggle any pins. 217 | # 218 | # If inverted is False (default): 219 | # DTR: connected to the bootloader pin 220 | # RTS: connected to !RESET 221 | # If inverted is True, pin connections are the other way round 222 | if inverted: 223 | set_bootloader_pin = self.sp.setRTS 224 | set_reset_pin = self.sp.setDTR 225 | else: 226 | set_bootloader_pin = self.sp.setDTR 227 | set_reset_pin = self.sp.setRTS 228 | 229 | if sonoff_usb: 230 | mdebug(5,'sonoff') 231 | # this bootloader toggle is added specifically for the 232 | # ITead Sonoff Zigbee 3.0 USB Dongle. This dongle has an odd 233 | # connection between RTS DTR and reset and IO15 (imply gate): 234 | # DTR RTS | RST IO15 235 | # 1 1 | 1 1 236 | # 0 0 | 1 1 237 | # 1 0 | 0 1 238 | # 0 1 | 1 0 239 | set_bootloader_pin(0) 240 | set_reset_pin(1) 241 | 242 | set_bootloader_pin(1) 243 | set_reset_pin(0) 244 | else: 245 | set_bootloader_pin(1 if not dtr_active_high else 0) 246 | set_reset_pin(0) 247 | set_reset_pin(1) 248 | 249 | set_reset_pin(0) 250 | # Make sure the pin is still asserted when the chip 251 | # comes out of reset. This fixes an issue where 252 | # there wasn't enough delay here on Mac. 253 | time.sleep(0.002) 254 | set_bootloader_pin(0 if not dtr_active_high else 1) 255 | 256 | # Some boards have a co-processor that detects this sequence here and 257 | # then drives the main chip's BSL enable and !RESET pins. Depending on 258 | # board design and co-processor behaviour, the !RESET pin may get 259 | # asserted after we have finished the sequence here. In this case, we 260 | # need a small delay so as to avoid trying to talk to main chip before 261 | # it has actually entered its bootloader mode. 262 | # 263 | # See contiki-os/contiki#1533 264 | time.sleep(0.1) 265 | 266 | def close(self): 267 | self.sp.close() 268 | 269 | def _wait_for_ack(self, info="", timeout=1): 270 | stop = time.time() + timeout 271 | got = bytearray(2) 272 | while got[-2] != 00 or got[-1] not in (CommandInterface.ACK_BYTE, 273 | CommandInterface.NACK_BYTE): 274 | got += self._read(1) 275 | if time.time() > stop: 276 | raise CmdException("Timeout waiting for ACK/NACK after '%s'" 277 | % (info,)) 278 | 279 | # Our bytearray's length is: 2 initial bytes + 2 bytes for the ACK/NACK 280 | # plus a possible N-4 additional (buffered) bytes 281 | mdebug(10, "Got %d additional bytes before ACK/NACK" % (len(got) - 4,)) 282 | 283 | # wait for ask 284 | ask = got[-1] 285 | 286 | if ask == CommandInterface.ACK_BYTE: 287 | # ACK 288 | return 1 289 | elif ask == CommandInterface.NACK_BYTE: 290 | # NACK 291 | mdebug(10, "Target replied with a NACK during %s" % info) 292 | return 0 293 | 294 | # Unknown response 295 | mdebug(10, "Unrecognised response 0x%x to %s" % (ask, info)) 296 | return 0 297 | 298 | def _encode_addr(self, addr): 299 | byte3 = (addr >> 0) & 0xFF 300 | byte2 = (addr >> 8) & 0xFF 301 | byte1 = (addr >> 16) & 0xFF 302 | byte0 = (addr >> 24) & 0xFF 303 | return bytes([byte0, byte1, byte2, byte3]) 304 | 305 | def _decode_addr(self, byte0, byte1, byte2, byte3): 306 | return ((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | (byte0 << 0)) 307 | 308 | def _calc_checks(self, cmd, addr, size): 309 | return ((sum(bytearray(self._encode_addr(addr))) + 310 | sum(bytearray(self._encode_addr(size))) + 311 | cmd) & 0xFF) 312 | 313 | def _write(self, data, is_retry=False): 314 | if type(data) == int: 315 | assert data < 256 316 | goal = 1 317 | written = self.sp.write(bytes([data])) 318 | elif type(data) == bytes or type(data) == bytearray: 319 | goal = len(data) 320 | written = self.sp.write(data) 321 | else: 322 | raise CmdException("Internal Error. Bad data type: {}" 323 | .format(type(data))) 324 | 325 | if written < goal: 326 | mdebug(10, "*** Only wrote {} of target {} bytes" 327 | .format(written, goal)) 328 | if is_retry and written == 0: 329 | raise CmdException("Failed to write data on the serial bus") 330 | mdebug(10, "*** Retrying write for remainder") 331 | if type(data) == int: 332 | return self._write(data, is_retry=True) 333 | else: 334 | return self._write(data[written:], is_retry=True) 335 | 336 | def _read(self, length): 337 | return bytearray(self.sp.read(length)) 338 | 339 | def sendAck(self): 340 | self._write(0x00) 341 | self._write(0xCC) 342 | return 343 | 344 | def sendNAck(self): 345 | self._write(0x00) 346 | self._write(0x33) 347 | return 348 | 349 | def receivePacket(self): 350 | # stop = time.time() + 5 351 | # got = None 352 | # while not got: 353 | got = self._read(2) 354 | # if time.time() > stop: 355 | # break 356 | 357 | # if not got: 358 | # raise CmdException("No response to %s" % info) 359 | 360 | size = got[0] # rcv size 361 | chks = got[1] # rcv checksum 362 | data = bytearray(self._read(size - 2)) # rcv data 363 | 364 | mdebug(10, "*** received %x bytes" % size) 365 | if chks == sum(data) & 0xFF: 366 | self.sendAck() 367 | return data 368 | else: 369 | self.sendNAck() 370 | # TODO: retry receiving! 371 | raise CmdException("Received packet checksum error") 372 | return 0 373 | 374 | def sendSynch(self): 375 | cmd = 0x55 376 | 377 | # flush serial input buffer for first ACK reception 378 | self.sp.flushInput() 379 | 380 | mdebug(10, "*** sending synch sequence") 381 | self._write(cmd) # send U 382 | self._write(cmd) # send U 383 | return self._wait_for_ack("Synch (0x55 0x55)", 2) 384 | 385 | def checkLastCmd(self): 386 | stat = self.cmdGetStatus() 387 | if not (stat): 388 | raise CmdException("No response from target on status request. " 389 | "(Did you disable the bootloader?)") 390 | 391 | if stat[0] == COMMAND_RET_SUCCESS: 392 | mdebug(10, "Command Successful") 393 | return 1 394 | else: 395 | stat_str = RETURN_CMD_STRS.get(stat[0], None) 396 | if stat_str == None: 397 | mdebug(0, "Warning: unrecognized status returned " 398 | "0x%x" % stat[0]) 399 | else: 400 | mdebug(0, "Target returned: 0x%x, %s" % (stat[0], stat_str)) 401 | return 0 402 | 403 | def cmdPing(self): 404 | cmd = 0x20 405 | lng = 3 406 | 407 | self._write(lng) # send size 408 | self._write(cmd) # send checksum 409 | self._write(cmd) # send data 410 | 411 | mdebug(10, "*** Ping command (0x20)") 412 | if self._wait_for_ack("Ping (0x20)"): 413 | return self.checkLastCmd() 414 | 415 | def cmdReset(self): 416 | cmd = 0x25 417 | lng = 3 418 | 419 | self._write(lng) # send size 420 | self._write(cmd) # send checksum 421 | self._write(cmd) # send data 422 | 423 | mdebug(10, "*** Reset command (0x25)") 424 | if self._wait_for_ack("Reset (0x25)"): 425 | return 1 426 | 427 | def cmdGetChipId(self): 428 | cmd = 0x28 429 | lng = 3 430 | 431 | self._write(lng) # send size 432 | self._write(cmd) # send checksum 433 | self._write(cmd) # send data 434 | 435 | mdebug(10, "*** GetChipId command (0x28)") 436 | if self._wait_for_ack("Get ChipID (0x28)"): 437 | # 4 byte answ, the 2 LSB hold chip ID 438 | version = self.receivePacket() 439 | if self.checkLastCmd(): 440 | assert len(version) == 4, ("Unreasonable chip " 441 | "id: %s" % repr(version)) 442 | mdebug(10, " Version 0x%02X%02X%02X%02X" % tuple(version)) 443 | chip_id = (version[2] << 8) | version[3] 444 | return chip_id 445 | else: 446 | raise CmdException("GetChipID (0x28) failed") 447 | 448 | def cmdGetStatus(self): 449 | cmd = 0x23 450 | lng = 3 451 | 452 | self._write(lng) # send size 453 | self._write(cmd) # send checksum 454 | self._write(cmd) # send data 455 | 456 | mdebug(10, "*** GetStatus command (0x23)") 457 | if self._wait_for_ack("Get Status (0x23)"): 458 | stat = self.receivePacket() 459 | return stat 460 | 461 | def cmdSetXOsc(self): 462 | cmd = 0x29 463 | lng = 3 464 | 465 | self._write(lng) # send size 466 | self._write(cmd) # send checksum 467 | self._write(cmd) # send data 468 | 469 | mdebug(10, "*** SetXOsc command (0x29)") 470 | if self._wait_for_ack("SetXOsc (0x29)"): 471 | return 1 472 | # UART speed (needs) to be changed! 473 | 474 | def cmdRun(self, addr): 475 | cmd = 0x22 476 | lng = 7 477 | 478 | self._write(lng) # send length 479 | self._write(self._calc_checks(cmd, addr, 0)) # send checksum 480 | self._write(cmd) # send cmd 481 | self._write(self._encode_addr(addr)) # send addr 482 | 483 | mdebug(10, "*** Run command(0x22)") 484 | return 1 485 | 486 | def cmdEraseMemory(self, addr, size): 487 | cmd = 0x26 488 | lng = 11 489 | 490 | self._write(lng) # send length 491 | self._write(self._calc_checks(cmd, addr, size)) # send checksum 492 | self._write(cmd) # send cmd 493 | self._write(self._encode_addr(addr)) # send addr 494 | self._write(self._encode_addr(size)) # send size 495 | 496 | mdebug(10, "*** Erase command(0x26)") 497 | if self._wait_for_ack("Erase memory (0x26)", 10): 498 | return self.checkLastCmd() 499 | 500 | def cmdBankErase(self): 501 | cmd = 0x2C 502 | lng = 3 503 | 504 | self._write(lng) # send length 505 | self._write(cmd) # send checksum 506 | self._write(cmd) # send cmd 507 | 508 | mdebug(10, "*** Bank Erase command(0x2C)") 509 | if self._wait_for_ack("Bank Erase (0x2C)", 10): 510 | return self.checkLastCmd() 511 | 512 | def cmdCRC32(self, addr, size): 513 | cmd = 0x27 514 | lng = 11 515 | 516 | self._write(lng) # send length 517 | self._write(self._calc_checks(cmd, addr, size)) # send checksum 518 | self._write(cmd) # send cmd 519 | self._write(self._encode_addr(addr)) # send addr 520 | self._write(self._encode_addr(size)) # send size 521 | 522 | mdebug(10, "*** CRC32 command(0x27)") 523 | if self._wait_for_ack("Get CRC32 (0x27)", 1): 524 | crc = self.receivePacket() 525 | if self.checkLastCmd(): 526 | return self._decode_addr(crc[3], crc[2], crc[1], crc[0]) 527 | 528 | def cmdCRC32CC26xx(self, addr, size): 529 | cmd = 0x27 530 | lng = 15 531 | 532 | self._write(lng) # send length 533 | self._write(self._calc_checks(cmd, addr, size)) # send checksum 534 | self._write(cmd) # send cmd 535 | self._write(self._encode_addr(addr)) # send addr 536 | self._write(self._encode_addr(size)) # send size 537 | self._write(self._encode_addr(0x00000000)) # send number of reads 538 | 539 | mdebug(10, "*** CRC32 command(0x27)") 540 | if self._wait_for_ack("Get CRC32 (0x27)", 1): 541 | crc = self.receivePacket() 542 | if self.checkLastCmd(): 543 | return self._decode_addr(crc[3], crc[2], crc[1], crc[0]) 544 | 545 | def cmdDownload(self, addr, size): 546 | cmd = 0x21 547 | lng = 11 548 | 549 | if (size % 4) != 0: # check for invalid data lengths 550 | raise Exception('Invalid data size: %i. ' 551 | 'Size must be a multiple of 4.' % size) 552 | 553 | self._write(lng) # send length 554 | self._write(self._calc_checks(cmd, addr, size)) # send checksum 555 | self._write(cmd) # send cmd 556 | self._write(self._encode_addr(addr)) # send addr 557 | self._write(self._encode_addr(size)) # send size 558 | 559 | mdebug(10, "*** Download command (0x21)") 560 | if self._wait_for_ack("Download (0x21)", 2): 561 | return self.checkLastCmd() 562 | 563 | def cmdSendData(self, data): 564 | cmd = 0x24 565 | lng = len(data)+3 566 | # TODO: check total size of data!! max 252 bytes! 567 | 568 | self._write(lng) # send size 569 | self._write((sum(bytearray(data))+cmd) & 0xFF) # send checksum 570 | self._write(cmd) # send cmd 571 | self._write(bytearray(data)) # send data 572 | 573 | mdebug(10, "*** Send Data (0x24)") 574 | if self._wait_for_ack("Send data (0x24)", 10): 575 | return self.checkLastCmd() 576 | 577 | def cmdMemRead(self, addr): # untested 578 | cmd = 0x2A 579 | lng = 8 580 | 581 | self._write(lng) # send length 582 | self._write(self._calc_checks(cmd, addr, 4)) # send checksum 583 | self._write(cmd) # send cmd 584 | self._write(self._encode_addr(addr)) # send addr 585 | self._write(4) # send width, 4 bytes 586 | 587 | mdebug(10, "*** Mem Read (0x2A)") 588 | if self._wait_for_ack("Mem Read (0x2A)", 1): 589 | data = self.receivePacket() 590 | if self.checkLastCmd(): 591 | # self._decode_addr(ord(data[3]), 592 | # ord(data[2]),ord(data[1]),ord(data[0])) 593 | return data 594 | 595 | def cmdMemReadCC26xx(self, addr): 596 | cmd = 0x2A 597 | lng = 9 598 | 599 | self._write(lng) # send length 600 | self._write(self._calc_checks(cmd, addr, 2)) # send checksum 601 | self._write(cmd) # send cmd 602 | self._write(self._encode_addr(addr)) # send addr 603 | self._write(1) # send width, 4 bytes 604 | self._write(1) # send number of reads 605 | 606 | mdebug(10, "*** Mem Read (0x2A)") 607 | if self._wait_for_ack("Mem Read (0x2A)", 1): 608 | data = self.receivePacket() 609 | if self.checkLastCmd(): 610 | return data 611 | 612 | def cmdMemWrite(self, addr, data, width): 613 | if width != len(data): 614 | raise ValueError("width does not match len(data)") 615 | if width != 1 and width != 4: 616 | raise ValueError("width must be 1 or 4") 617 | 618 | cmd = 0x2B 619 | lng = 8 + len(data) 620 | 621 | content = ( 622 | bytearray([cmd]) 623 | + self._encode_addr(addr) 624 | + bytearray([1 if (width == 4) else 0]) 625 | + bytearray(data) 626 | ) 627 | 628 | self._write(lng) # send length 629 | self._write(sum(content) & 0xFF) # send checksum 630 | self._write(content) 631 | 632 | mdebug(10, "*** Mem write (0x2B)") 633 | if self._wait_for_ack("Mem Write (0x2B)", 2): 634 | return self.checkLastCmd() 635 | 636 | 637 | # Complex commands section 638 | 639 | def writeMemory(self, addr, data): 640 | lng = len(data) 641 | # amount of data bytes transferred per packet (theory: max 252 + 3) 642 | trsf_size = 248 643 | empty_packet = bytearray((0xFF,) * trsf_size) 644 | 645 | # Boot loader enable check 646 | # TODO: implement check for all chip sizes & take into account partial 647 | # firmware uploads 648 | if (lng == 524288): # check if file is for 512K model 649 | # check the boot loader enable bit (only for 512K model) 650 | if not ((data[524247] & (1 << 4)) >> 4): 651 | if not (conf['force'] or 652 | query_yes_no("The boot loader backdoor is not enabled " 653 | "in the firmware you are about to write " 654 | "to the target. You will NOT be able to " 655 | "reprogram the target using this tool if " 656 | "you continue! " 657 | "Do you want to continue?", "no")): 658 | raise Exception('Aborted by user.') 659 | 660 | mdebug(5, "Writing %(lng)d bytes starting at address 0x%(addr)08X" % 661 | {'lng': lng, 'addr': addr}) 662 | 663 | offs = 0 664 | addr_set = 0 665 | 666 | # check if amount of remaining data is less then packet size 667 | while lng > trsf_size: 668 | # skip packets filled with 0xFF 669 | if data[offs:offs+trsf_size] != empty_packet: 670 | if addr_set != 1: 671 | # set starting address if not set 672 | self.cmdDownload(addr, lng) 673 | addr_set = 1 674 | mdebug(5, " Write %(len)d bytes at 0x%(addr)08X" 675 | % {'addr': addr, 'len': trsf_size}, '\r') 676 | sys.stdout.flush() 677 | 678 | # send next data packet 679 | self.cmdSendData(data[offs:offs+trsf_size]) 680 | else: # skipped packet, address needs to be set 681 | addr_set = 0 682 | 683 | offs = offs + trsf_size 684 | addr = addr + trsf_size 685 | lng = lng - trsf_size 686 | 687 | mdebug(5, "Write %(len)d bytes at 0x%(addr)08X" % {'addr': addr, 688 | 'len': lng}) 689 | self.cmdDownload(addr, lng) 690 | return self.cmdSendData(data[offs:offs+lng]) # send last data packet 691 | 692 | 693 | class Chip(object): 694 | def __init__(self, command_interface): 695 | self.command_interface = command_interface 696 | 697 | # Some defaults. The child can override. 698 | self.flash_start_addr = 0x00000000 699 | self.has_cmd_set_xosc = False 700 | self.page_size = 2048 701 | 702 | 703 | def page_align_up(self, value): 704 | return int(math.ceil(value / self.page_size) * self.page_size) 705 | 706 | 707 | def page_align_down(self, value): 708 | return int(math.floor(value / self.page_size) * self.page_size) 709 | 710 | 711 | def page_to_addr(self, pages): 712 | addresses = [] 713 | for page in pages: 714 | addresses.append(int(device.flash_start_addr) + 715 | int(page)*self.page_size) 716 | return addresses 717 | 718 | def crc(self, address, size): 719 | return getattr(self.command_interface, self.crc_cmd)(address, size) 720 | 721 | def disable_bootloader(self): 722 | if not (conf['force'] or 723 | query_yes_no("Disabling the bootloader will prevent you from " 724 | "using this script until you re-enable the " 725 | "bootloader using JTAG. Do you want to continue?", 726 | "no")): 727 | raise Exception('Aborted by user.') 728 | 729 | pattern = struct.pack('> 4 754 | if 0 < self.size <= 4: 755 | self.size *= 0x20000 # in bytes 756 | else: 757 | self.size = 0x10000 # in bytes 758 | self.bootloader_address = self.flash_start_addr + self.size - ccfg_len 759 | 760 | sram = (((model[2] << 8) | model[3]) & 0x380) >> 7 761 | sram = (2 - sram) << 3 if sram <= 1 else 32 # in KB 762 | 763 | pg = self.command_interface.cmdMemRead(FLASH_CTRL_DIECFG2) 764 | pg_major = (pg[2] & 0xF0) >> 4 765 | if pg_major == 0: 766 | pg_major = 1 767 | pg_minor = pg[2] & 0x0F 768 | 769 | ti_oui = bytearray([0x00, 0x12, 0x4B]) 770 | ieee_addr = self.command_interface.cmdMemRead( 771 | addr_ieee_address_primary) 772 | ieee_addr_end = self.command_interface.cmdMemRead( 773 | addr_ieee_address_primary + 4) 774 | if ieee_addr[:3] == ti_oui: 775 | ieee_addr += ieee_addr_end 776 | else: 777 | ieee_addr = ieee_addr_end + ieee_addr 778 | 779 | mdebug(5, "CC2538 PG%d.%d: %dKB Flash, %dKB SRAM, CCFG at 0x%08X" 780 | % (pg_major, pg_minor, self.size >> 10, sram, 781 | self.bootloader_address)) 782 | mdebug(5, "Primary IEEE Address: %s" 783 | % (':'.join('%02X' % x for x in ieee_addr))) 784 | 785 | def erase(self): 786 | mdebug(5, "Erasing %s bytes starting at address 0x%08X" 787 | % (self.size, self.flash_start_addr)) 788 | return self.command_interface.cmdEraseMemory(self.flash_start_addr, 789 | self.size) 790 | 791 | def read_memory(self, addr): 792 | # CC2538's COMMAND_MEMORY_READ sends each 4-byte number in inverted 793 | # byte order compared to what's written on the device 794 | data = self.command_interface.cmdMemRead(addr) 795 | return bytearray([data[x] for x in range(3, -1, -1)]) 796 | 797 | 798 | class CC26xx(Chip): 799 | # Class constants 800 | MISC_CONF_1 = 0x500010A0 801 | PROTO_MASK_BLE = 0x01 802 | PROTO_MASK_IEEE = 0x04 803 | PROTO_MASK_BOTH = 0x05 804 | 805 | def __init__(self, command_interface): 806 | super(CC26xx, self).__init__(command_interface) 807 | self.bootloader_dis_val = 0x00000000 808 | self.crc_cmd = "cmdCRC32CC26xx" 809 | self.page_size = 4096 810 | 811 | ICEPICK_DEVICE_ID = 0x50001318 812 | FCFG_USER_ID = 0x50001294 813 | PRCM_RAMHWOPT = 0x40082250 814 | FLASH_SIZE = 0x4003002C 815 | addr_ieee_address_primary = 0x500012F0 816 | ccfg_len = 88 817 | ieee_address_secondary_offset = 0x20 818 | bootloader_dis_offset = 0x30 819 | sram = "Unknown" 820 | 821 | # Determine CC13xx vs CC26xx via ICEPICK_DEVICE_ID::WAFER_ID and store 822 | # PG revision 823 | device_id = self.command_interface.cmdMemReadCC26xx(ICEPICK_DEVICE_ID) 824 | wafer_id = (((device_id[3] & 0x0F) << 16) + 825 | (device_id[2] << 8) + 826 | (device_id[1] & 0xF0)) >> 4 827 | pg_rev = (device_id[3] & 0xF0) >> 4 828 | 829 | # Read FCFG1_USER_ID to get the package and supported protocols 830 | user_id = self.command_interface.cmdMemReadCC26xx(FCFG_USER_ID) 831 | package = {0x00: '4x4mm', 832 | 0x01: '5x5mm', 833 | 0x02: '7x7mm', 834 | 0x03: 'Wafer', 835 | 0x04: '2.7x2.7', 836 | 0x05: '7x7mm Q1', 837 | }.get(user_id[2] & 0x03, "Unknown") 838 | 839 | protocols = user_id[1] >> 4 840 | 841 | # We can now detect the exact device 842 | if wafer_id == 0xB99A: 843 | chip = self._identify_cc26xx(pg_rev, protocols) 844 | elif wafer_id == 0xB9BE: 845 | chip = self._identify_cc13xx(pg_rev, protocols) 846 | elif wafer_id == 0xBB41 or wafer_id == 0xBB77 or wafer_id == 0xBB7A: 847 | chip = self._identify_cc13xx(pg_rev, protocols) 848 | self.page_size = 8192 849 | 850 | # Read flash size, calculate and store bootloader disable address 851 | self.size = self.command_interface.cmdMemReadCC26xx( 852 | FLASH_SIZE)[0] * self.page_size 853 | self.bootloader_address = self.size - ccfg_len + bootloader_dis_offset 854 | self.addr_ieee_address_secondary = (self.size - ccfg_len + 855 | ieee_address_secondary_offset) 856 | 857 | # RAM size 858 | ramhwopt_size = self.command_interface.cmdMemReadCC26xx( 859 | PRCM_RAMHWOPT)[0] & 3 860 | if ramhwopt_size == 3: 861 | sram = "20KB" 862 | elif ramhwopt_size == 2: 863 | sram = "16KB" 864 | else: 865 | sram = "Unknown" 866 | 867 | # Primary IEEE address. Stored with the MSB at the high address 868 | ieee_addr = self.command_interface.cmdMemReadCC26xx( 869 | addr_ieee_address_primary + 4)[::-1] 870 | ieee_addr += self.command_interface.cmdMemReadCC26xx( 871 | addr_ieee_address_primary)[::-1] 872 | 873 | mdebug(5, "%s (%s): %dKB Flash, %s SRAM, CCFG.BL_CONFIG at 0x%08X" 874 | % (chip, package, self.size >> 10, sram, 875 | self.bootloader_address)) 876 | mdebug(5, "Primary IEEE Address: %s" 877 | % (':'.join('%02X' % x for x in ieee_addr))) 878 | 879 | def _identify_cc26xx(self, pg, protocols): 880 | chips_dict = { 881 | CC26xx.PROTO_MASK_IEEE: 'CC2630', 882 | CC26xx.PROTO_MASK_BLE: 'CC2640', 883 | CC26xx.PROTO_MASK_BOTH: 'CC2650', 884 | } 885 | 886 | chip_str = chips_dict.get(protocols & CC26xx.PROTO_MASK_BOTH, "Unknown") 887 | 888 | if pg == 1: 889 | pg_str = "PG1.0" 890 | elif pg == 3: 891 | pg_str = "PG2.0" 892 | elif pg == 7: 893 | pg_str = "PG2.1" 894 | elif pg == 8 or pg == 0x0B: 895 | # CC26x0 PG2.2+ or CC26x0R2 896 | rev_minor = self.command_interface.cmdMemReadCC26xx( 897 | CC26xx.MISC_CONF_1)[0] 898 | if rev_minor == 0xFF: 899 | rev_minor = 0x00 900 | 901 | if pg == 8: 902 | # CC26x0 903 | pg_str = "PG2.%d" % (2 + rev_minor,) 904 | elif pg == 0x0B: 905 | # HW revision R2, update Chip name 906 | chip_str += 'R2' 907 | pg_str = "PG%d.%d" % (1 + (rev_minor // 10), rev_minor % 10) 908 | 909 | return "%s %s" % (chip_str, pg_str) 910 | 911 | def _identify_cc13xx(self, pg, protocols): 912 | chip_str = "CC1310" 913 | if protocols & CC26xx.PROTO_MASK_IEEE == CC26xx.PROTO_MASK_IEEE: 914 | chip_str = "CC1350" 915 | 916 | if pg == 0: 917 | pg_str = "PG1.0" 918 | elif pg == 2 or pg == 3 or pg == 1: 919 | rev_minor = self.command_interface.cmdMemReadCC26xx( 920 | CC26xx.MISC_CONF_1)[0] 921 | if rev_minor == 0xFF: 922 | rev_minor = 0x00 923 | pg_str = "PG2.%d" % (rev_minor,) 924 | 925 | return "%s %s" % (chip_str, pg_str) 926 | 927 | def erase(self): 928 | mdebug(5, "Erasing all main bank flash sectors") 929 | return self.command_interface.cmdBankErase() 930 | 931 | def read_memory(self, addr): 932 | # CC26xx COMMAND_MEMORY_READ returns contents in the same order as 933 | # they are stored on the device 934 | return self.command_interface.cmdMemReadCC26xx(addr) 935 | 936 | 937 | def query_yes_no(question, default="yes"): 938 | valid = {"yes": True, 939 | "y": True, 940 | "ye": True, 941 | "no": False, 942 | "n": False} 943 | if default == None: 944 | prompt = " [y/n] " 945 | elif default == "yes": 946 | prompt = " [Y/n] " 947 | elif default == "no": 948 | prompt = " [y/N] " 949 | else: 950 | raise ValueError("invalid default answer: '%s'" % default) 951 | 952 | while True: 953 | sys.stdout.write(question + prompt) 954 | choice = input().lower() 955 | if default != None and choice == '': 956 | return valid[default] 957 | elif choice in valid: 958 | return valid[choice] 959 | else: 960 | sys.stdout.write("Please respond with 'yes' or 'no' " 961 | "(or 'y' or 'n').\n") 962 | 963 | 964 | # Convert the entered IEEE address into an integer 965 | def parse_ieee_address(inaddr): 966 | try: 967 | return int(inaddr, 16) 968 | except ValueError: 969 | # inaddr is not a hex string, look for other formats 970 | if ':' in inaddr: 971 | bytes = inaddr.split(':') 972 | elif '-' in inaddr: 973 | bytes = inaddr.split('-') 974 | if len(bytes) != 8: 975 | raise ValueError("Supplied IEEE address does not contain 8 bytes") 976 | addr = 0 977 | for i, b in zip(range(8), bytes): 978 | try: 979 | addr += int(b, 16) << (56-(i*8)) 980 | except ValueError: 981 | raise ValueError("IEEE address contains invalid bytes") 982 | return addr 983 | 984 | 985 | def _parse_range_values(device, values): 986 | if len(values) and len(values) < 3: 987 | page_addr_range = [] 988 | try: 989 | for value in values: 990 | try: 991 | if int(value) % int(device.page_size) != 0: 992 | raise ValueError("Supplied addresses are not page_size: " 993 | "{} aligned".format(device.page_size)) 994 | page_addr_range.append(int(value)) 995 | except ValueError: 996 | if int(value, 16) % int(device.page_size) != 0: 997 | raise ValueError("Supplied addresses are not page_size: " 998 | "{} aligned".format(device.page_size)) 999 | page_addr_range.append(int(value, 16)) 1000 | return page_addr_range 1001 | except ValueError: 1002 | raise ValueError("Supplied value is not a page or an address") 1003 | else: 1004 | raise ValueError("Supplied range is neither a page or address range") 1005 | 1006 | 1007 | def parse_page_address_range(device, pg_range): 1008 | """Convert the address/page range into a start address and byte length""" 1009 | values = pg_range.split(',') 1010 | page_addr = [] 1011 | # check if first argument is character 1012 | if values[0].isalpha(): 1013 | values[0].lower() 1014 | if values[0] == 'p' or values[0] == 'page': 1015 | if values[0] == 'p': 1016 | values[1:] = device.page_to_addr(values[1:]) 1017 | elif values[0] != 'a' and values[0] != 'address': 1018 | raise ValueError("Prefix is neither a(address) or p(page)") 1019 | page_addr.extend(_parse_range_values(device, values[1:])) 1020 | else: 1021 | page_addr.extend(_parse_range_values(device, values)) 1022 | if len(page_addr) == 1: 1023 | return [page_addr[0], device.page_size] 1024 | else: 1025 | return [page_addr[0], (page_addr[1] - page_addr[0])] 1026 | 1027 | 1028 | def version(): 1029 | # Get the version using "git describe". 1030 | try: 1031 | p = Popen(['git', 'describe', '--tags', '--match', '[0-9]*'], 1032 | stdout=PIPE, stderr=PIPE) 1033 | p.stderr.close() 1034 | line = p.stdout.readlines()[0] 1035 | return line.decode('utf-8').strip() 1036 | except: 1037 | # We're not in a git repo, or git failed, use fixed version string. 1038 | return __version__ 1039 | 1040 | 1041 | def cli_setup(): 1042 | parser = argparse.ArgumentParser() 1043 | 1044 | parser.add_argument('-q', action='store_true', help='Quiet') 1045 | parser.add_argument('-V', action='store_true', help='Verbose') 1046 | parser.add_argument('-f', '--force', action='store_true', help='Force operation(s) without asking any questions') 1047 | parser.add_argument('-e', '--erase', action='store_true', help='Mass erase') 1048 | parser.add_argument('-E', '--erase-page', help='Receives an address(a) range or page(p) range, default is address(a) eg: -E a,0x00000000,0x00001000, -E p,1,4') 1049 | parser.add_argument('-w', '--write', action='store_true', help='Write') 1050 | parser.add_argument('-W', '--erase-write', action='store_true', help='Write after erasing section to write to (avoids a mass erase). Rounds up section to erase if not page aligned.') 1051 | parser.add_argument('-v', '--verify', action='store_true', help='Verify (CRC32 check)') 1052 | parser.add_argument('-r', '--read', action='store_true', help='Read') 1053 | parser.add_argument('-l', '--len', type=int, default=0x80000, help='Length of read') 1054 | parser.add_argument('-p', '--port', help='Serial port (default: first USB-like port in /dev)') 1055 | parser.add_argument('-b', '--baud', type=int, help=f"Baud speed (default: {DEFAULT_BAUD})") 1056 | parser.add_argument('-a', '--address', type=int, help='Target address') 1057 | parser.add_argument('-i', '--ieee-address', help='Set the secondary 64 bit IEEE address') 1058 | parser.add_argument('--bootloader-active-high', action='store_true', help='Use active high signals to enter bootloader') 1059 | parser.add_argument('--bootloader-invert-lines', action='store_true', help='Inverts the use of RTS and DTR to enter bootloader') 1060 | parser.add_argument('--bootloader-sonoff-usb', action='store_true', help='Toggles RTS and DTR in the correct pattern for Sonoff USB dongle') 1061 | parser.add_argument('-D', '--disable-bootloader', action='store_true', help='After finishing, disable the bootloader') 1062 | parser.add_argument('--version', action='version', version='%(prog)s ' + version()) 1063 | parser.add_argument('file') 1064 | 1065 | return parser.parse_args() 1066 | 1067 | if __name__ == "__main__": 1068 | args = cli_setup() 1069 | 1070 | force_speed = False 1071 | 1072 | if args.baud: 1073 | force_speed = True 1074 | else: 1075 | args.baud = DEFAULT_BAUD 1076 | 1077 | if args.V: 1078 | QUIET = 10 1079 | elif args.q: 1080 | QUIET = 0 1081 | 1082 | try: 1083 | if (args.write and args.read) or (args.erase_write and args.read): 1084 | if not (args.force or 1085 | query_yes_no("You are reading and writing to the same " 1086 | "file. This will overwrite your input file. " 1087 | "Do you want to continue?", "no")): 1088 | raise Exception('Aborted by user.') 1089 | if ((args.erase and args.read) or (args.erase_page and args.read) 1090 | and not (args.write or args.erase_write)): 1091 | if not (args.force or 1092 | query_yes_no("You are about to erase your target before " 1093 | "reading. Do you want to continue?", "no")): 1094 | raise Exception('Aborted by user.') 1095 | 1096 | if (args.read and not (args.write or args.erase_write) 1097 | and args.verify): 1098 | raise Exception('Verify after read not implemented.') 1099 | 1100 | if args.len < 0: 1101 | raise Exception('Length must be positive but %d was provided' 1102 | % (args.len,)) 1103 | 1104 | # Try and find the port automatically 1105 | if not args.port: 1106 | ports = [] 1107 | 1108 | # Get a list of all USB-like names in /dev 1109 | for name in ['ttyACM', 1110 | 'tty.usbserial', 1111 | 'ttyUSB', 1112 | 'tty.usbmodem', 1113 | 'tty.SLAB_USBtoUART']: 1114 | ports.extend(glob.glob('/dev/%s*' % name)) 1115 | 1116 | ports = sorted(ports) 1117 | 1118 | if ports: 1119 | # Found something - take it 1120 | args.port = ports[0] 1121 | else: 1122 | raise Exception('No serial port found.') 1123 | 1124 | cmd = CommandInterface() 1125 | cmd.open(args.port, args.baud) 1126 | cmd.invoke_bootloader(args.bootloader_active_high, 1127 | args.bootloader_invert_lines, 1128 | args.bootloader_sonoff_usb) 1129 | mdebug(5, "Opening port %(port)s, baud %(baud)d" 1130 | % {'port': args.port, 'baud': args.baud}) 1131 | if args.write or args.erase_write or args.verify: 1132 | mdebug(5, "Reading data from %s" % args.file) 1133 | firmware = FirmwareFile(args.file) 1134 | 1135 | mdebug(5, "Connecting to target...") 1136 | 1137 | if not cmd.sendSynch(): 1138 | raise CmdException("Can't connect to target. Ensure boot loader " 1139 | "is started. (no answer on synch sequence)") 1140 | 1141 | # if (cmd.cmdPing() != 1): 1142 | # raise CmdException("Can't connect to target. Ensure boot loader " 1143 | # "is started. (no answer on ping command)") 1144 | 1145 | chip_id = cmd.cmdGetChipId() 1146 | chip_id_str = CHIP_ID_STRS.get(chip_id, None) 1147 | 1148 | if chip_id_str == None: 1149 | mdebug(10, ' Unrecognized chip ID. Trying CC13xx/CC26xx') 1150 | device = CC26xx(cmd) 1151 | else: 1152 | mdebug(10, " Target id 0x%x, %s" % (chip_id, chip_id_str)) 1153 | device = CC2538(cmd) 1154 | 1155 | # Choose a good default address unless the user specified -a 1156 | if not args.address: 1157 | args.address = device.flash_start_addr 1158 | 1159 | if not force_speed and device.has_cmd_set_xosc: 1160 | if cmd.cmdSetXOsc(): # switch to external clock source 1161 | cmd.close() 1162 | args.baud = 1000000 1163 | cmd.open(args.port, args.baud) 1164 | mdebug(6, "Opening port %(port)s, baud %(baud)d" 1165 | % {'port': args.port, 'baud': args.baud}) 1166 | mdebug(6, "Reconnecting to target at higher speed...") 1167 | if (cmd.sendSynch() != 1): 1168 | raise CmdException("Can't connect to target after clock " 1169 | "source switch. (Check external " 1170 | "crystal)") 1171 | else: 1172 | raise CmdException("Can't switch target to external clock " 1173 | "source. (Try forcing speed)") 1174 | 1175 | if args.erase: 1176 | mdebug(5, " Performing mass erase") 1177 | if device.erase(): 1178 | mdebug(5, " Erase done") 1179 | else: 1180 | raise CmdException("Erase failed") 1181 | 1182 | if args.erase_page: 1183 | erase_range = parse_page_address_range(device, args.erase_page) 1184 | mdebug(5, "Erasing %d bytes at addres 0x%x" 1185 | % (erase_range[1], erase_range[0])) 1186 | cmd.cmdEraseMemory(erase_range[0], erase_range[1]) 1187 | mdebug(5, " Partial erase done ") 1188 | 1189 | if args.write: 1190 | # TODO: check if boot loader back-door is open, need to read 1191 | # flash size first to get address 1192 | if cmd.writeMemory(args.address, firmware.bytes): 1193 | mdebug(5, " Write done ") 1194 | else: 1195 | raise CmdException("Write failed ") 1196 | 1197 | if args.erase_write: 1198 | # TODO: check if boot loader back-door is open, need to read 1199 | # flash size first to get address 1200 | # Round up to ensure page alignment 1201 | erase_len = device.page_align_up(len(firmware.bytes)) 1202 | erase_len = min(erase_len, device.size) 1203 | if cmd.cmdEraseMemory(args.address, erase_len): 1204 | mdebug(5, " Erase before write done ") 1205 | if cmd.writeMemory(args.address, firmware.bytes): 1206 | mdebug(5, " Write done ") 1207 | else: 1208 | raise CmdException("Write failed ") 1209 | 1210 | if args.verify: 1211 | mdebug(5, "Verifying by comparing CRC32 calculations.") 1212 | 1213 | crc_local = firmware.crc32() 1214 | # CRC of target will change according to length input file 1215 | crc_target = device.crc(args.address, len(firmware.bytes)) 1216 | 1217 | if crc_local == crc_target: 1218 | mdebug(5, " Verified (match: 0x%08x)" % crc_local) 1219 | else: 1220 | cmd.cmdReset() 1221 | raise Exception("NO CRC32 match: Local = 0x%x, " 1222 | "Target = 0x%x" % (crc_local, crc_target)) 1223 | 1224 | if args.ieee_address != 0: 1225 | ieee_addr = parse_ieee_address(args.ieee_address) 1226 | mdebug(5, "Setting IEEE address to %s" 1227 | % (':'.join(['%02x' % b 1228 | for b in struct.pack('>Q', ieee_addr)]))) 1229 | ieee_addr_bytes = struct.pack('> 2): 1248 | # reading 4 bytes at a time 1249 | rdata = device.read_memory(args.address + (i * 4)) 1250 | mdebug(5, " 0x%x: 0x%02x%02x%02x%02x" 1251 | % (args.address + (i * 4), rdata[0], rdata[1], 1252 | rdata[2], rdata[3]), '\r') 1253 | f.write(rdata) 1254 | f.close() 1255 | mdebug(5, " Read done ") 1256 | 1257 | if args.disable_bootloader: 1258 | device.disable_bootloader() 1259 | 1260 | cmd.cmdReset() 1261 | 1262 | except Exception as err: 1263 | if QUIET >= 10: 1264 | traceback.print_exc() 1265 | exit('ERROR: %s' % str(err)) 1266 | --------------------------------------------------------------------------------