├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── cc3200tool ├── __init__.py ├── cc.py └── dll │ ├── rbtl3100.dll │ ├── rbtl3100s.dll │ ├── rbtl3101.dll │ ├── rbtl3101_121.dll │ └── rbtl3101_132.dll ├── scripts └── cc3200tool └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: SciLor # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | *.py[c|o] 3 | *.egg-info/ 4 | cc3200tool-env/ 5 | scripts/extract/ 6 | build/ 7 | cc3200tool/time 8 | cc3200tool/sys 9 | cc3200tool/struct 10 | cc3200tool/serial 11 | cc3200tool/os 12 | cc3200tool/math 13 | cc3200tool/logging 14 | cc3200tool/json 15 | test/ 16 | .vscode/ 17 | tmp/* 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CC3200 Tool v1.2.4 2 | 3 | A small tool to read and write files in TI's CC3200 SimpleLink (TM) filesystem. 4 | Partial support for the CC32XX (CC3230/CC3235) series (via flash image) 5 | 6 | Copyright (C) 2016-2020 Allterco Robotics 7 | 8 | Copyright (C) 2020- Team RevvoX (0xbadbee, g3gg0) 9 | 10 | ![](https://img.shields.io/badge/license-GPL_2-green.svg "License") 11 | 12 | ## Rationale 13 | 14 | The only other tool which can officially do this is Uniflash, but good luck 15 | using that on a modern Linux system. 16 | 17 | There is also `cc3200prog` which [Energia](http://energia.nu) sneak in their toolchain tarball, 18 | but it's not open source and can only write to `/sys/mcuimg.bin`. 19 | 20 | Finally, there's the great guys at [Cesanta](https://www.cesanta.com/) 21 | who reversed the CC3200 bootloader 22 | protocol just enough to make day-to-day development on the platform possible. 23 | However, their tool is specific to [smart.js](https://www.cesanta.com/products/smart-js) 24 | and feeds on specially-crafted zip archives. 25 | 26 | This tool is based on the work done by Cesanta but is written in Python and 27 | exposes a generic cli interface for uploading user files and application binaries 28 | on a CC3200-attached serial flash. 29 | 30 | `cc3200tool` can upload NWP/MAC/PHY firmwares (`/sys/servicepack.ucf`), but it seems 31 | this only works on a clean FS. The tool also implements the functionality 32 | described in TI's Application Note [CC3100/CC3200 Embedded Programming](http://www.ti.com/tool/embedded-programming). 33 | 34 | ## Installation 35 | 36 | This runs on Python >=3.6 with recent [pySerial](https://github.com/pyserial/pyserial). 37 | 38 | To install, if you have pip and want system-wide: 39 | 40 | pip install git+git://github.com/toniebox-reverse-engineering/cc3200tool.git 41 | 42 | or clone this repǫ 43 | 44 | git clone http://github.com/toniebox-reverse-engineering/cc3200tool.git 45 | cd cc3200tool 46 | pip3 install . 47 | 48 | then it's just like any other python package: 49 | 50 | python setup.py install # as root, system-wide 51 | 52 | # or in a virtualenv with pip 53 | virtualenv env && ./env/bin/activate 54 | pip install -e . 55 | # then get updates with 56 | git pull 57 | 58 | ## Usage 59 | 60 | You need a serial port connected to the target's UART interface. For 61 | programming to work, SOP2 needs to be asserted (i.e. tied to VCC) and a reset 62 | has to be peformed to switch the chip in bootloader mode. `cc3200tool` can 63 | optionally use the RTS and DTR lines of the serial port controller to 64 | automatically perform these actions via the `--sop2` and `--reset` options. 65 | 66 | See `cc3200tool -h` and `cc3200tool -h` for complete description 67 | of arguments. Some examples: 68 | 69 | # upload an application 70 | cc3200tool -p /dev/ttyUSB2 --sop2 ~dtr --reset prompt \ 71 | write_file ./exe/myapp.bin /sys/mcuimg.bin 72 | 73 | # format and upload an application binary 74 | cc3200tool -p /dev/ttyUSB2 \ 75 | format_flash --size 1M \ 76 | write_file exe/program.bin /sys/mcuimg.bin 77 | 78 | # dump a file on stdout 79 | cc3200tool read_file /sys/mcuimg.bin - 80 | 81 | # format the flash, upload a servciepack and two files 82 | cc3200tool -p /dev/ttyUSB2 --sop2 ~rts --reset dtr \ 83 | format_flash --size=1M \ 84 | write_file --file-size=0x20000 \ 85 | --signature ../servicepack-ota/ota_1.0.1.6-2.6.0.5.ucf.signed.bin \ 86 | ../servicepack-ota/ota_1.0.1.6-2.6.0.5.ucf.ucf /sys/servicepack.ucf \ 87 | write_file ../application_bootloader/gcc/exe/application_bootloader.bin /sys/mcuimg.bin \ 88 | write_file yourapp.bin /sys/mcuimg1.bin 89 | 90 | # list file and filesystem statistics (occupied and free block sequences) 91 | cc3200tool -p /dev/ttyUSB2 list_filesystem 92 | 93 | # Reads all files to a directory and creates subdirecty structure 94 | cc3200tool -p /dev/ttyUSB2 read_all_files extract/ 95 | 96 | # Writes all files from a directory and its subdirectories (add --simulate to skip writing) 97 | cc3200tool -p /dev/ttyUSB2 write_all_files extract/ 98 | 99 | # list filesystem from a flashdump 100 | cc3200tool -if cc3200-flash.bin list_filesystem 101 | 102 | # list filesystem from a cc3230/cc3235 flashdump 103 | cc3200tool -if cc32xx-flash.bin -d cc32xx list_filesystem 104 | 105 | # dump a file from a flashdump 106 | cc3200tool -if cc3200-flash.bin read_file /sys/mcuimg.bin 107 | 108 | # Reads all files to a directory and creates subdirecty structure from a flashdump 109 | cc3200tool -if cc3200-flash.bin read_all_files extract/ 110 | 111 | # Replace the existing ca.der on the flash (only works if the new file isn't bigger than the old one, experimental!) 112 | cc3200tool -if cc32xx-flash.bin -of cc32xx-flash.custom.bin -d cc32xx write_file customca.der /cert/ca.der 113 | 114 | -------------------------------------------------------------------------------- /cc3200tool/__init__.py: -------------------------------------------------------------------------------- 1 | from .cc import * 2 | -------------------------------------------------------------------------------- /cc3200tool/cc.py: -------------------------------------------------------------------------------- 1 | # 2 | # cc3200tool - work with TI's CC3200 SimpleLink (TM) filesystem. 3 | # Copyright (C) 2016-2020 Allterco Robotics 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | 20 | import sys 21 | import os 22 | import tempfile 23 | import time 24 | import argparse 25 | import struct 26 | import math 27 | import logging 28 | from contextlib import contextmanager 29 | from pkgutil import get_data 30 | from collections import namedtuple 31 | import json 32 | 33 | import serial 34 | 35 | log = logging.getLogger() 36 | logging.basicConfig(stream=sys.stderr, level=logging.INFO, 37 | format="%(asctime)-15s -- %(message)s") 38 | 39 | CC3200_BAUD = 921600 40 | 41 | # erasing blocks is time consuming and depends on flash type 42 | # so separate timeout value is used 43 | ERASE_TIMEOUT = 120 44 | 45 | OPCODE_START_UPLOAD = b'\x21' 46 | OPCODE_FINISH_UPLOAD = b'\x22' 47 | OPCODE_GET_LAST_STATUS = b'\x23' 48 | OPCODE_FILE_CHUNK = b'\x24' 49 | OPCODE_GET_STORAGE_LIST = b'\x27' 50 | OPCODE_FORMAT_FLASH = b'\x28' 51 | OPCODE_GET_FILE_INFO = b'\x2A' 52 | OPCODE_READ_FILE_CHUNK = b'\x2B' 53 | OPCODE_RAW_STORAGE_READ = b'\x2C' 54 | OPCODE_RAW_STORAGE_WRITE = b'\x2D' 55 | OPCODE_ERASE_FILE = b'\x2E' 56 | OPCODE_GET_VERSION_INFO = b'\x2F' 57 | OPCODE_RAW_STORAGE_ERASE = b'\x30' 58 | OPCODE_GET_STORAGE_INFO = b'\x31' 59 | OPCODE_EXEC_FROM_RAM = b'\x32' 60 | OPCODE_SWITCH_2_APPS = b'\x33' 61 | 62 | STORAGE_ID_SRAM = 0x0 63 | STORAGE_ID_SFLASH = 0x2 64 | 65 | FLASH_BLOCK_SIZES = [0x100, 0x400, 0x1000, 0x4000, 0x10000] 66 | 67 | SLFS_SIZE_MAP = { 68 | "512": 512, 69 | "1M": 1024, 70 | "2M": 2 * 1024, 71 | "4M": 4 * 1024, 72 | "8M": 8 * 1024, 73 | "16M": 16 * 1024, 74 | } 75 | 76 | SLFS_BLOCK_SIZE = 4096 77 | 78 | # defines from cc3200-sdk/simplelink/include/fs.h 79 | SLFS_FILE_OPEN_FLAG_COMMIT = 0x1 # /* MIRROR - for fail safe */ 80 | SLFS_FILE_OPEN_FLAG_SECURE = 0x2 # /* SECURE */ 81 | SLFS_FILE_OPEN_FLAG_NO_SIGNATURE_TEST = 0x4 # /* Relevant to secure file only */ 82 | SLFS_FILE_OPEN_FLAG_STATIC = 0x8 # /* Relevant to secure file only */ 83 | SLFS_FILE_OPEN_FLAG_VENDOR = 0x10 # /* Relevant to secure file only */ 84 | SLFS_FILE_PUBLIC_WRITE = 0x20 # /* Relevant to secure file only, the file can be opened for write without Token */ 85 | SLFS_FILE_PUBLIC_READ = 0x40 # /* Relevant to secure file only, the file can be opened for read without Token */ 86 | 87 | SLFS_MODE_OPEN_READ = 0 88 | SLFS_MODE_OPEN_WRITE = 1 89 | SLFS_MODE_OPEN_CREATE = 2 90 | SLFS_MODE_OPEN_WRITE_CREATE_IF_NOT_EXIST = 3 91 | 92 | 93 | def hexify(s): 94 | return " ".join([hex(x) for x in s]) 95 | 96 | 97 | Pincfg = namedtuple('Pincfg', ['invert', 'pin']) 98 | 99 | 100 | def pinarg(extra=None): 101 | choices = ['dtr', 'rts', 'none'] 102 | if extra: 103 | choices.extend(extra) 104 | 105 | def _parse(apin): 106 | invert = False 107 | if apin.startswith('~'): 108 | invert = True 109 | apin = apin[1:] 110 | if apin not in choices: 111 | raise argparse.ArgumentTypeError(f"{apin} not one of {choices}") 112 | return Pincfg(invert, apin) 113 | 114 | return _parse 115 | 116 | 117 | def auto_int(x): 118 | return int(x, 0) 119 | 120 | class PathType(object): 121 | def __init__(self, exists=True, type='file', dash_ok=True): 122 | '''exists: 123 | True: a path that does exist 124 | False: a path that does not exist, in a valid parent directory 125 | None: don't care 126 | type: file, dir, symlink, None, or a function returning True for valid paths 127 | None: don't care 128 | dash_ok: whether to allow "-" as stdin/stdout''' 129 | 130 | assert exists in (True, False, None) 131 | assert type in ('file','dir','symlink',None) or hasattr(type,'__call__') 132 | 133 | self._exists = exists 134 | self._type = type 135 | self._dash_ok = dash_ok 136 | 137 | def __call__(self, string): 138 | if string=='-': 139 | # the special argument "-" means sys.std{in,out} 140 | if self._type == 'dir': 141 | raise CC3200Error('standard input/output (-) not allowed as directory path') 142 | elif self._type == 'symlink': 143 | raise CC3200Error('standard input/output (-) not allowed as symlink path') 144 | elif not self._dash_ok: 145 | raise CC3200Error('standard input/output (-) not allowed') 146 | else: 147 | e = os.path.exists(string) 148 | if self._exists==True: 149 | if not e: 150 | raise CC3200Error("path does not exist: '%s'" % string) 151 | 152 | if self._type is None: 153 | pass 154 | elif self._type=='file': 155 | if not os.path.isfile(string): 156 | raise CC3200Error("path is not a file: '%s'" % string) 157 | elif self._type=='symlink': 158 | if not os.path.symlink(string): 159 | raise CC3200Error("path is not a symlink: '%s'" % string) 160 | elif self._type=='dir': 161 | if not os.path.isdir(string): 162 | raise CC3200Error("path is not a directory: '%s'" % string) 163 | elif not self._type(string): 164 | raise CC3200Error("path not valid: '%s'" % string) 165 | else: 166 | if self._exists==False and e: 167 | raise CC3200Error("path exists: '%s'" % string) 168 | 169 | p = os.path.dirname(os.path.normpath(string)) or '.' 170 | if not os.path.isdir(p): 171 | raise CC3200Error("parent path is not a directory: '%s'" % p) 172 | elif not os.path.exists(p): 173 | raise CC3200Error("parent directory does not exist: '%s'" % p) 174 | 175 | return string 176 | 177 | 178 | # TODO: replace argparse.FileType('rb') with manual file handling 179 | parser = argparse.ArgumentParser(description='Serial flash utility for CC3200') 180 | 181 | parser.add_argument( 182 | "-p", "--port", type=str, default="/dev/ttyUSB0", 183 | help="The serial port to use") 184 | parser.add_argument( 185 | "-if", "--image_file", type=str, default=None, 186 | help="Use a image file instead of serial link (read)") 187 | parser.add_argument( 188 | "-of", "--output_file", type=str, default=None, 189 | help="Use a image file instead of serial link (write)") 190 | parser.add_argument( 191 | "--reset", type=pinarg(['prompt']), default="none", 192 | help="dtr, rts, none or prompt, optinally prefixed by ~ to invert") 193 | parser.add_argument( 194 | "--sop2", type=pinarg(), default="none", 195 | help="dtr, rts or none, optinally prefixed by ~ to invert") 196 | parser.add_argument( 197 | "--erase_timeout", type=auto_int, default=ERASE_TIMEOUT, 198 | help="Specify block erase timeout for all operations which involve block erasing") 199 | parser.add_argument( 200 | "--reboot-to-app", action="store_true", 201 | help="When finished, reboot to the application") 202 | parser.add_argument( 203 | "-d", "--device", type=str, default="cc3200", 204 | help="Device to select cc3200/cc32xx (to decide which offsets to use)") 205 | 206 | subparsers = parser.add_subparsers(dest="cmd") 207 | 208 | parser_format_flash = subparsers.add_parser( 209 | "format_flash", help="Format the flash memory") 210 | parser_format_flash.add_argument( 211 | "-s", "--size", choices=list(SLFS_SIZE_MAP.keys()), default="1M") 212 | 213 | parser_erase_file = subparsers.add_parser( 214 | "erase_file", help="Erase a file from the SL filesystem") 215 | parser_erase_file.add_argument( 216 | "filename", help="file on the target to be removed") 217 | 218 | parser_write_file = subparsers.add_parser( 219 | "write_file", help="Upload a file on the SL filesystem") 220 | parser_write_file.add_argument( 221 | "local_file", type=argparse.FileType('rb'), 222 | help="file on the local file system") 223 | parser_write_file.add_argument( 224 | "cc_filename", help="file name to write on the target") 225 | parser_write_file.add_argument( 226 | "--signature", type=argparse.FileType('rb'), 227 | help="file which contains the 256 bytes of signature for secured files") 228 | parser_write_file.add_argument( 229 | "--file-size", type=auto_int, default=0, 230 | help="allows allocating more space than needed for this upload") 231 | parser_write_file.add_argument( 232 | "--commit-flag", action="store_true", 233 | help="enables fail safe MIRROR feature") 234 | parser_write_file.add_argument( 235 | "--file-id", type=auto_int, default=-1, help="if filename not available you can read a file by its id (image_file only!)") 236 | parser_write_file.add_argument( 237 | "--no-verify", type=bool, default=False, 238 | help="do not perform a read of the written data to verify") 239 | 240 | parser_read_file = subparsers.add_parser( 241 | "read_file", help="read a file from the SL filesystem") 242 | parser_read_file.add_argument( 243 | "cc_filename", help="file to read from the target") 244 | parser_read_file.add_argument( 245 | "local_file", type=argparse.FileType('w+b'), 246 | help="local path to store the file contents in") 247 | parser_read_file.add_argument( 248 | "--file-id", type=auto_int, default=-1, help="if filename not available you can read a file by its id") 249 | parser_read_file.add_argument( 250 | "--inactive", action="store_true", 251 | help="read from inactive FAT copy") 252 | parser_read_file.add_argument( 253 | "--no-verify", type=bool, default=False, 254 | help="do not perform a second read of the data to verify") 255 | 256 | 257 | parser_write_flash = subparsers.add_parser( 258 | "write_flash", help="Write a Gang image on the flash") 259 | parser_write_flash.add_argument( 260 | "gang_image_file", type=argparse.FileType('rb'), 261 | help="gang image file prepared with Uniflash") 262 | parser_write_flash.add_argument( 263 | "--no-erase", type=bool, default=False, 264 | help="do not perform an erase before write (for blank chips)") 265 | parser_write_flash.add_argument( 266 | "--no-verify", type=bool, default=False, 267 | help="do not perform a read of the written data to verify") 268 | 269 | parser_read_flash = subparsers.add_parser( 270 | "read_flash", help="Read SFFS contents into the file") 271 | parser_read_flash.add_argument( 272 | "dump_file", type=argparse.FileType('w+b'), 273 | help="path to store the SFFS dump") 274 | parser_read_flash.add_argument( 275 | "--offset", type=auto_int, default=0, 276 | help="starting offset (default is 0)") 277 | parser_read_flash.add_argument( 278 | "--size", type=auto_int, default=-1, 279 | help="dump size (default is complete SFFS)") 280 | parser_read_flash.add_argument( 281 | "--ignore-max-size", type=bool, default=False, 282 | help="ignore the maximum size of the flash") 283 | parser_read_flash.add_argument( 284 | "--no-verify", type=bool, default=False, 285 | help="do not perform a second read of the data to verify") 286 | 287 | 288 | parser_list_filesystem = subparsers.add_parser( 289 | "list_filesystem", 290 | help="List SFFS contents and statistics (blocks total/used, inter-file gaps, etc)") 291 | parser_list_filesystem.add_argument( 292 | "--json-output", action="store_true", 293 | help="output in JSON format to stdout") 294 | parser_list_filesystem.add_argument( 295 | "--inactive", action="store_true", 296 | help="output inactive FAT copy") 297 | parser_list_filesystem.add_argument( 298 | "--extended", action="store_true", 299 | help="Read file header and show size in bytes") 300 | 301 | parser_read_all_files = subparsers.add_parser( 302 | "read_all_files", 303 | help="Reads all files into a subfolder structure") 304 | parser_read_all_files.add_argument( 305 | #"local_dir", type=PathType(exists=False, type='dir'), 306 | "local_dir", 307 | help="local path to store the files in") 308 | parser_read_all_files.add_argument( 309 | "--by-file-id", action="store_true", 310 | help="Read unknown filenames by its id") 311 | parser_read_all_files.add_argument( 312 | "--all-by-file-id", action="store_true", 313 | help="Read all filenames by its id") 314 | parser_read_all_files.add_argument( 315 | "--inactive", action="store_true", 316 | help="read from inactive FAT copy") 317 | parser_read_all_files.add_argument( 318 | "--no-verify", type=bool, default=False, 319 | help="do not perform a second read of the data to verify") 320 | 321 | parser_write_all_files = subparsers.add_parser( 322 | "write_all_files", 323 | help="Writes all files from a subfolder structure") 324 | parser_write_all_files.add_argument( 325 | "local_dir", type=PathType(exists=True, type='dir'), 326 | help="local path to read the files from") 327 | parser_write_all_files.add_argument( 328 | "--simulate", action="store_false", 329 | help="List all files to be written and skip writing them") 330 | parser_write_all_files.add_argument( 331 | "--no-verify", type=bool, default=False, 332 | help="do not perform a read of the written data to verify") 333 | 334 | parser_dll_data_test = subparsers.add_parser( 335 | "dll_data_test", 336 | help="Tests the dll_data function") 337 | 338 | def dll_data(fname): 339 | data = None 340 | path = os.path.join(os.path.dirname(__file__), os.path.join('dll', fname)) 341 | if os.path.exists(path): 342 | log.info("Reading %s from file %s" % (fname, path)) 343 | data = open(path, 'rb').read() 344 | if data is None: 345 | log.info("Reading %s from package" % fname) 346 | data = get_data('cc3200tool', os.path.join('dll', fname)) 347 | if data is None: 348 | raise CC3200Error("could not find dll file %s" % fname) 349 | return data 350 | 351 | 352 | class CC3200Error(Exception): 353 | pass 354 | 355 | 356 | class CC3x00VersionInfo(object): 357 | def __init__(self, bootloader, nwp, mac, phy, chip_type): 358 | self.bootloader = bootloader 359 | self.nwp = nwp 360 | self.mac = mac 361 | self.phy = phy 362 | self.chip_type = chip_type 363 | 364 | @property 365 | def is_cc3200(self): 366 | return (self.chip_type[0] & 0x10) != 0 367 | 368 | @classmethod 369 | def from_packet(cls, data): 370 | bootloader = tuple(data[0:4]) 371 | nwp = tuple(data[4:8]) 372 | mac = tuple(data[8:12]) 373 | phy = tuple(data[12:16]) 374 | chip_type = tuple(data[16:20]) 375 | return cls(bootloader, nwp, mac, phy, chip_type) 376 | 377 | def __repr__(self): 378 | return "CC3x00VersionInfo({}, {}, {}, {}, {})".format( 379 | self.bootloader, self.nwp, self.mac, self.phy, self.chip_type) 380 | 381 | 382 | class CC3x00StorageList(object): 383 | FLASH_BIT = 0x02 384 | SFLASH_BIT = 0x04 385 | SRAM_BIT = 0x80 386 | 387 | def __init__(self, value): 388 | self.value = value 389 | 390 | @property 391 | def flash(self): 392 | return (self.value & self.FLASH_BIT) != 0 393 | 394 | @property 395 | def sflash(self): 396 | return (self.value & self.SFLASH_BIT) != 0 397 | 398 | @property 399 | def sram(self): 400 | return (self.value & self.SRAM_BIT) != 0 401 | 402 | def __repr__(self): 403 | return "{}({})".format(self.__class__.__name__, hex(self.value)) 404 | 405 | 406 | class CC3x00StorageInfo(object): 407 | def __init__(self, block_size, block_count): 408 | self.block_size = block_size 409 | self.block_count = block_count 410 | 411 | @classmethod 412 | def from_packet(cls, data): 413 | bsize, bcount = struct.unpack(">HH", data[:4]) 414 | return cls(bsize, bcount) 415 | 416 | def __repr__(self): 417 | return "{}(block_size={}, block_count={})".format( 418 | self.__class__.__name__, self.block_size, self.block_count) 419 | 420 | 421 | class CC3x00Status(object): 422 | def __init__(self, value): 423 | self.value = value 424 | 425 | @property 426 | def is_ok(self): 427 | return self.value == 0x40 428 | 429 | @classmethod 430 | def from_packet(cls, packet): 431 | return cls(packet[3]) 432 | 433 | 434 | class CC3x00FileInfo(object): 435 | def __init__(self, exists, size=0): 436 | self.exists = exists 437 | self.size = size 438 | 439 | @classmethod 440 | def from_packet(cls, data): 441 | exists = data[0] == 0x01 442 | size = struct.unpack(">I", data[4:8])[0] 443 | return cls(exists, size) 444 | 445 | 446 | class CC3x00SffsStatsFileEntry(object): 447 | def __init__(self, index, start_block, size_blocks, mirrored, flags, fname, header=None): 448 | self.index = index 449 | self.start_block = start_block 450 | self.size_blocks = size_blocks 451 | self.mirrored = mirrored 452 | self.flags = flags 453 | self.fname = fname 454 | 455 | self.total_blocks = self.size_blocks 456 | if self.mirrored: 457 | self.total_blocks = self.total_blocks * 2 458 | 459 | self.header = header 460 | self.magic = None 461 | self.size = 0 462 | if header != None: 463 | self.read_header(header) 464 | 465 | def read_header(self, header): 466 | self.header = header 467 | self.size = header[2]<<16 | header[1]<<8 | header[0]<<0 468 | self.magic = bytearray(header[3:]) 469 | 470 | def get_magic(self): 471 | ##fileheader[6:7] 4c 53 472 | return ''.join('{:02x}'.format(x) for x in self.magic) 473 | 474 | 475 | 476 | class CC3x00SffsHole(object): 477 | def __init__(self, start_block, size_blocks): 478 | self.start_block = start_block 479 | self.size_blocks = size_blocks 480 | 481 | 482 | class CC3x00SffsHeader(object): 483 | SFFS_HEADER_SIGNATURE = 0x534c 484 | 485 | def __init__(self, fat_index, fat_bytes, storage_info): 486 | self.is_valid = False 487 | self.storage_info = storage_info 488 | 489 | if len(fat_bytes) != storage_info.block_size: 490 | raise CC3200Error("incorrect FAT size") 491 | 492 | """ 493 | perform just a basic parsing for now, a caller will select a more 494 | relevant fat and then call get_sffs_stats() in order to initiate 495 | complete parsing 496 | """ 497 | 498 | fat_commit_revision, header_sign = struct.unpack("> 4 568 | start_block_msb = flags_sb_msb & 0xf 569 | 570 | mirrored = (flags & 0x4) == 0 571 | start_block = (start_block_msb << 8) + start_block_lsb 572 | 573 | meta2_e = meta2[i * 4 : (i + 1) * 4] 574 | fname_offset, fname_len = struct.unpack(" prev_end_block: 606 | hole = CC3x00SffsHole(prev_end_block, snippet[0] - prev_end_block - 1) 607 | self.holes.append(hole) 608 | prev_end_block = snippet[0] + snippet[1] 609 | 610 | def print_sffs_info(self, extended=False): 611 | log.info("Serial Flash block size:\t%d bytes", self.block_size) 612 | log.info("Serial Flash capacity:\t%d blocks", self.block_count) 613 | log.info("") 614 | 615 | if extended: 616 | log.info("\tfile\tstart\tsize\tsize\tfail\tflags\ttotal\tmagic\t\tfilename") 617 | log.info("\tindex\tblock\t[BLKs]\t[bytes]\tsafe\t\t[BLKs]") 618 | log.info("-------------------------------------------------------------------------------------------------") 619 | log.info("\tN/A\t0\t5\tN/A\tN/A\t5\tN/A\tN/A\t\tFATFS") 620 | else: 621 | log.info("\tfile\tstart\tsize\tfail\tflags\ttotal\tfilename") 622 | log.info("\tindex\tblock\t[BLKs]\tsafe\t[BLKs]") 623 | log.info("----------------------------------------------------------------------------") 624 | log.info("\tN/A\t0\t5\tN/A\tN/A\t5\tFATFS") 625 | 626 | for f in self.files: 627 | if extended: 628 | log.info("\t%d\t%d\t%d\t%d\t%s\t0x%x\t%d\t%s\t%s" % 629 | (f.index, f.start_block, f.size_blocks, f.size, 630 | f.mirrored and "yes" or "no", 631 | f.flags, f.total_blocks, f.get_magic(), f.fname)) 632 | else: 633 | log.info("\t%d\t%d\t%d\t%s\t0x%x\t%d\t%s" % 634 | (f.index, f.start_block, f.size_blocks, 635 | f.mirrored and "yes" or "no", 636 | f.flags, f.total_blocks, f.fname)) 637 | 638 | log.info("") 639 | log.info(" Flash usage") 640 | log.info("-------------------------") 641 | log.info("used space:\t%d blocks", self.used_blocks) 642 | log.info("free space:\t%d blocks", 643 | self.block_count - self.used_blocks) 644 | 645 | for h in self.holes: 646 | log.info("memory hole:\t[%d-%d]", h.start_block, 647 | h.start_block + h.size_blocks) 648 | 649 | def print_sffs_info_short(self): 650 | log.info("FAT r%d, num files: %d, used/free blocks: %d/%d", 651 | self.fat_commit_revision, len(self.files), self.used_blocks, 652 | self.block_count - self.used_blocks) 653 | 654 | def print_sffs_info_json(self): 655 | print(json.dumps(self, cls=CustomJsonEncoder)) 656 | 657 | 658 | class CustomJsonEncoder(json.JSONEncoder): 659 | def default(self, o): 660 | return o.__dict__ 661 | 662 | 663 | class CC3200Connection(object): 664 | SFFS_FAT_METADATA2_CC3200_OFFSET = 0x774 665 | SFFS_FAT_METADATA2_CC32XX_OFFSET = 0x2000 666 | SFFS_FAT_METADATA2_LENGTH = 0x1000 667 | SFFS_FAT_PART_OFFSET = 0x1000 668 | SFFS_FAT_FILE_HEADER_SIZE = 0x8 669 | 670 | TIMEOUT = 5 671 | DEFAULT_SLFS_SIZE = "1M" 672 | 673 | def __init__(self, port, reset=None, sop2=None, erase_timeout=ERASE_TIMEOUT, device=None, image_file=None, output_file=None): 674 | self.port = port 675 | if not self.port is None: 676 | port.timeout = self.TIMEOUT 677 | self._device = device 678 | self._reset = reset 679 | self._sop2 = sop2 680 | self._erase_timeout = erase_timeout 681 | self._image_file = None 682 | self._output_file = None 683 | 684 | self.vinfo = None 685 | self.vinfo_apps = None 686 | 687 | if not image_file is None: 688 | self._image_file = open(image_file, 'rb') 689 | if not output_file is None: 690 | self._output_file = open(output_file, 'w+b') 691 | 692 | def copy_input_file_to_output_file(self): 693 | if not self._image_file is None or not self._output_file is None: 694 | self._image_file.seek(0) 695 | data = self._image_file.read() 696 | self._output_file.seek(0) 697 | self._output_file.write(data) 698 | 699 | @contextmanager 700 | def _serial_timeout(self, timeout=None): 701 | if timeout is None: 702 | yield self.port 703 | return 704 | if timeout == self.port.timeout: 705 | yield self.port 706 | return 707 | orig_timeout, self.port.timeout = self.port.timeout, timeout 708 | yield self.port 709 | self.port.timeout = orig_timeout 710 | 711 | def _set_sop2(self, level): 712 | if self._sop2.pin == "none": 713 | return 714 | 715 | toset = level ^ self._sop2.invert 716 | if self._sop2.pin == 'dtr': 717 | self.port.dtr = toset 718 | if self._sop2.pin == 'rts': 719 | self.port.rts = toset 720 | 721 | def _do_reset(self, sop2): 722 | self._set_sop2(sop2) 723 | 724 | if self._reset.pin == "none": 725 | return 726 | 727 | if self._reset.pin == "prompt": 728 | print("Reset the device with SOP2 {}asserted and press Enter".format( 729 | '' if sop2 else 'de' 730 | )) 731 | input() 732 | return 733 | 734 | in_reset = True ^ self._reset.invert 735 | if self._reset.pin == 'dtr': 736 | self.port.dtr = in_reset 737 | time.sleep(.1) 738 | self.port.dtr = not in_reset 739 | 740 | if self._reset.pin == 'rts': 741 | self.port.rts = in_reset 742 | time.sleep(.1) 743 | self.port.rts = not in_reset 744 | 745 | def _read_ack(self, timeout=None): 746 | ack_bytes = [] 747 | with self._serial_timeout(timeout) as port: 748 | while True: 749 | b = port.read(1) 750 | if not b: 751 | log.error("timed out while waiting for ack") 752 | return False 753 | ack_bytes.append(b) 754 | if len(ack_bytes) > 2: 755 | ack_bytes.pop(0) 756 | if ack_bytes == [b'\x00', b'\xCC']: 757 | return True 758 | 759 | def _read_packet(self, timeout=None): 760 | with self._serial_timeout(timeout) as port: 761 | header = port.read(3) 762 | if len(header) != 3: 763 | raise CC3200Error("read_packed timed out on header") 764 | len_bytes = header[:2] 765 | csum_byte = header[2] 766 | 767 | data_len = struct.unpack(">H", len_bytes)[0] - 2 768 | with self._serial_timeout(timeout): 769 | data = self.port.read(data_len) 770 | 771 | if (len(data) != data_len): 772 | raise CC3200Error("did not get entire response") 773 | 774 | ccsum = sum(data) 775 | ccsum = ccsum & 0xff 776 | if ccsum != csum_byte: 777 | raise CC3200Error("rx csum failed") 778 | 779 | self._send_ack() 780 | return data 781 | 782 | def _send_packet(self, data, timeout=None): 783 | assert len(data) 784 | checksum = sum(data) 785 | len_blob = struct.pack(">H", len(data) + 2) 786 | csum = struct.pack("B", checksum & 0xff) 787 | self.port.write(len_blob + csum + data) 788 | if not self._read_ack(timeout): 789 | raise CC3200Error( 790 | f"No ack for packet opcode=0x{data[0]:02x}") 791 | 792 | def _send_ack(self): 793 | self.port.write(b'\x00\xCC') 794 | 795 | def _get_last_status(self): 796 | self._send_packet(OPCODE_GET_LAST_STATUS) 797 | 798 | if not self.port is None: 799 | status = self._read_packet() 800 | log.debug("get last status got %s", hexify(status)) 801 | return CC3x00Status(status[0]) 802 | 803 | return CC3x00Status(0) 804 | 805 | def _do_break(self, timeout): 806 | self.port.send_break(.2) 807 | return self._read_ack(timeout) 808 | 809 | def _try_breaking(self, tries=5, timeout=2): 810 | for _ in range(tries): 811 | if self._do_break(timeout): 812 | break 813 | else: 814 | raise CC3200Error("Did not get ACK on break condition") 815 | 816 | def _get_version(self): 817 | self._send_packet(OPCODE_GET_VERSION_INFO) 818 | 819 | if not self.port is None: 820 | version_data = self._read_packet() 821 | if len(version_data) != 28: 822 | raise CC3200Error(f"Version info should be 28 bytes, got {len(version_data)}") 823 | return CC3x00VersionInfo.from_packet(version_data) 824 | 825 | return CC3x00VersionInfo((0,4,1,2), (0,0,0,0), (0,0,0,0), (0,0,0,0), (16,0,0,0)) 826 | 827 | def _get_storage_list(self): 828 | log.info("Getting storage list...") 829 | if not self.port is None: 830 | self._send_packet(OPCODE_GET_STORAGE_LIST) 831 | with self._serial_timeout(.5): 832 | slist_byte = self.port.read(1) 833 | if len(slist_byte) != 1: 834 | raise CC3200Error("Did not receive storage list byte") 835 | return CC3x00StorageList(slist_byte[0]) 836 | 837 | return CC3x00StorageList(15) 838 | 839 | def _get_storage_info(self, storage_id=STORAGE_ID_SRAM): 840 | log.info("Getting storage info...") 841 | if not self.port is None: 842 | self._send_packet(OPCODE_GET_STORAGE_INFO + 843 | struct.pack(">I", storage_id)) 844 | sinfo = self._read_packet() 845 | if len(sinfo) < 4: 846 | raise CC3200Error(f"getting storage info got {len(sinfo)} bytes") 847 | log.info("storage #%d info bytes: %s", storage_id, ", " 848 | .join([hex(x) for x in sinfo])) 849 | return CC3x00StorageInfo.from_packet(sinfo) 850 | 851 | return CC3x00StorageInfo(SLFS_BLOCK_SIZE, 1024) #TODO: as parameter 852 | 853 | def _erase_blocks(self, start, count, storage_id=STORAGE_ID_SRAM): 854 | command = OPCODE_RAW_STORAGE_ERASE + \ 855 | struct.pack(">III", storage_id, start, count) 856 | self._send_packet(command, timeout=self._erase_timeout) 857 | 858 | def _send_chunk(self, offset, data, storage_id=STORAGE_ID_SRAM): 859 | if not self.port is None: 860 | command = OPCODE_RAW_STORAGE_WRITE + \ 861 | struct.pack(">III", storage_id, offset, len(data)) 862 | self._send_packet(command + data) 863 | return 864 | self._output_file.seek(offset) 865 | self._output_file.write(data) 866 | 867 | def _raw_write(self, offset, data, storage_id=STORAGE_ID_SRAM): 868 | slist = self._get_storage_list() 869 | if storage_id == STORAGE_ID_SFLASH and not slist.sflash: 870 | raise CC3200Error("no serial flash?!") 871 | if storage_id == STORAGE_ID_SRAM and not slist.sram: 872 | raise CC3200Error("no sram?!") 873 | 874 | chunk_size = 4080 875 | sent = 0 876 | while sent < len(data): 877 | chunk = data[sent:sent + chunk_size] 878 | self._send_chunk(offset + sent, chunk, storage_id) 879 | sent += len(chunk) 880 | 881 | def _raw_write_file(self, offset, filename, storage_id=STORAGE_ID_SRAM): 882 | with open(filename, 'r') as f: 883 | data = f.read() 884 | return self._raw_write(offset, data, storage_id) 885 | 886 | def _read_chunk(self, offset, size, storage_id=STORAGE_ID_SRAM): 887 | if not self.port is None: 888 | # log.info("Reading chunk at 0x%x size 0x%x..." % (offset, size)) 889 | command = OPCODE_RAW_STORAGE_READ + \ 890 | struct.pack(">III", storage_id, offset, size) 891 | self._send_packet(command) 892 | data = self._read_packet() 893 | if len(data) != size: 894 | raise CC3200Error("invalid received size: %d vs %d" % (len(data), size)) 895 | return data 896 | 897 | self._image_file.seek(offset) 898 | data = self._image_file.read(size) 899 | return data 900 | 901 | def _raw_read(self, offset, size, storage_id=STORAGE_ID_SRAM, sinfo=None, ignore_max_size=False): 902 | slist = self._get_storage_list() 903 | if storage_id == STORAGE_ID_SFLASH and not slist.sflash: 904 | raise CC3200Error("no serial flash?!") 905 | if storage_id == STORAGE_ID_SRAM and not slist.sram: 906 | raise CC3200Error("no sram?!") 907 | 908 | if not sinfo: 909 | sinfo = self._get_storage_info(storage_id) 910 | storage_size = sinfo.block_count * sinfo.block_size 911 | 912 | if ignore_max_size: 913 | storage_size = offset + size 914 | log.warning("Ignoring storage size limits") 915 | 916 | if offset > storage_size: 917 | raise CC3200Error("offset %d is bigger than available mem %d" % 918 | (offset, storage_size)) 919 | 920 | if size < 1: 921 | size = storage_size - offset 922 | log.info("Setting raw read size to maximum: %d", size) 923 | elif size + offset > storage_size: 924 | raise CC3200Error("size %d + offset %d is bigger than available mem %d" % 925 | (size, offset, storage_size)) 926 | 927 | log.info("Reading raw storage #%d start 0x%x, size 0x%x..." % 928 | (storage_id, offset, size)) 929 | 930 | # XXX 4096 works faster, but 256 was sniffed from the uniflash 931 | chunk_size = 4096 932 | rx_data = b'' 933 | while size - len(rx_data) > 0: 934 | rx_data += self._read_chunk(offset + len(rx_data), 935 | min(chunk_size, size - len(rx_data)), 936 | storage_id) 937 | sys.stderr.write('.') 938 | sys.stderr.flush() 939 | sys.stderr.write("\n") 940 | return rx_data 941 | 942 | def _exec_from_ram(self): 943 | self._send_packet(OPCODE_EXEC_FROM_RAM) 944 | 945 | def _get_file_info(self, filename, file_id=-1, inactive=False): 946 | if not self.port is None and file_id == -1: 947 | command = OPCODE_GET_FILE_INFO \ 948 | + struct.pack(">I", len(filename)) \ 949 | + filename.encode() 950 | self._send_packet(command) 951 | finfo = self._read_packet() 952 | if len(finfo) < 5: 953 | raise CC3200Error() 954 | return CC3x00FileInfo.from_packet(finfo) 955 | 956 | fat_info = self.get_fat_info(inactive=inactive) 957 | finfo = CC3x00FileInfo(exists=False, size=0) 958 | for file in fat_info.files: 959 | if file_id == -1: 960 | if file.fname == filename: 961 | finfo = CC3x00FileInfo(exists=True, size=file.size_blocks*SLFS_BLOCK_SIZE) 962 | break 963 | elif file.index == file_id: 964 | finfo = CC3x00FileInfo(exists=True, size=file.size_blocks*SLFS_BLOCK_SIZE) 965 | break 966 | return finfo 967 | 968 | 969 | def _open_file_for_write(self, filename, file_len, fs_flags=None): 970 | for bsize_idx, bsize in enumerate(FLASH_BLOCK_SIZES): 971 | if (bsize * 255) >= file_len: 972 | blocks = int(math.ceil(float(file_len) / bsize)) 973 | break 974 | else: 975 | raise CC3200Error("file is too big") 976 | 977 | fs_access = SLFS_MODE_OPEN_WRITE_CREATE_IF_NOT_EXIST 978 | flags = (((fs_access & 0x0f) << 12) | 979 | ((bsize_idx & 0x0f) << 8) | 980 | (blocks & 0xff)) 981 | 982 | if fs_flags is not None: 983 | flags |= (fs_flags & 0xff) << 16 984 | 985 | return self._open_file(filename, flags) 986 | 987 | def _open_file_for_read(self, filename): 988 | return self._open_file(filename, 0) 989 | 990 | def _open_file(self, filename, slfs_flags): 991 | command = OPCODE_START_UPLOAD + struct.pack(">II", slfs_flags, 0) + \ 992 | filename.encode() + b'\x00\x00' 993 | self._send_packet(command) 994 | 995 | token = self.port.read(4) 996 | if not len(token) == 4: 997 | raise CC3200Error("open") 998 | 999 | def _close_file(self, signature=None): 1000 | if signature is None: 1001 | signature = b'\x46' * 256 1002 | if len(signature) != 256: 1003 | raise CC3200Error("bad signature length") 1004 | command = OPCODE_FINISH_UPLOAD 1005 | command += b'\x00' * 63 1006 | command += signature 1007 | command += b'\x00' 1008 | self._send_packet(command) 1009 | s = self._get_last_status() 1010 | if not s.is_ok: 1011 | raise CC3200Error("closing file failed") 1012 | 1013 | def connect(self): 1014 | log.info("Connecting to target...") 1015 | self.port.flushInput() 1016 | self._do_reset(True) 1017 | self._try_breaking(tries=5, timeout=2) 1018 | log.info("Connected, reading version...") 1019 | self.vinfo = self._get_version() 1020 | 1021 | def reboot_to_app(self): 1022 | log.info("Rebooting to application") 1023 | self._do_reset(False) 1024 | 1025 | def switch_to_nwp_bootloader(self): 1026 | log.info("Switching to NWP bootloader...") 1027 | vinfo = self._get_version() 1028 | if not vinfo.is_cc3200: 1029 | log.debug("This looks like the NWP already") 1030 | return 1031 | 1032 | if vinfo.bootloader[1] < 3: 1033 | raise CC3200Error("Unsupported device") 1034 | 1035 | if vinfo.bootloader[1] == 3: 1036 | # cesanta upload and exec rbtl3101_132.dll for this version 1037 | # then do the UART switch 1038 | raise CC3200Error("Not yet supported device (bootloader=3)") 1039 | 1040 | self.switch_uart_to_apps() 1041 | 1042 | if vinfo.bootloader[1] == 3: 1043 | # should upload rbtl3100.dll 1044 | raise CC3200Error("Not yet supported device (NWP bootloader=3)") 1045 | 1046 | if vinfo.bootloader[1] >= 4: 1047 | log.info("Uploading rbtl3100s.dll...") 1048 | self._raw_write(0, dll_data('rbtl3100s.dll')) 1049 | self._exec_from_ram() 1050 | 1051 | if not self._read_ack(): 1052 | raise CC3200Error("got no ACK after exec from ram") 1053 | 1054 | def switch_uart_to_apps(self): 1055 | # ~ 1 sec delay by the APPS MCU 1056 | log.info("Switching UART to APPS...") 1057 | command = OPCODE_SWITCH_2_APPS + struct.pack(">I", 26666667) 1058 | self._send_packet(command) 1059 | log.info("Resetting communications ...") 1060 | time.sleep(1) 1061 | self._try_breaking() 1062 | self.vinfo_apps = self._get_version() 1063 | 1064 | def format_slfs(self, size=None): 1065 | if size is None: 1066 | size = self.DEFAULT_SLFS_SIZE 1067 | 1068 | if size not in SLFS_SIZE_MAP: 1069 | raise CC3200Error("invalid SLFS size") 1070 | 1071 | size = SLFS_SIZE_MAP[size] 1072 | 1073 | log.info("Formatting flash with size=%s", size) 1074 | command = OPCODE_FORMAT_FLASH \ 1075 | + struct.pack(">IIIII", 2, size//4, 0, 0, 2) 1076 | 1077 | self._send_packet(command) 1078 | 1079 | s = self._get_last_status() 1080 | if not s.is_ok: 1081 | raise CC3200Error("Format failed") 1082 | 1083 | def erase_file(self, filename, force=False): 1084 | if not force: 1085 | finfo = self._get_file_info(filename) 1086 | if not finfo.exists: 1087 | log.warn("File '%s' does not exist, won't erase", filename) 1088 | return 1089 | 1090 | log.info("Erasing file %s...", filename) 1091 | command = OPCODE_ERASE_FILE + struct.pack(">I", 0) + \ 1092 | filename.encode() + b'\x00' 1093 | self._send_packet(command) 1094 | s = self._get_last_status() 1095 | if not s.is_ok: 1096 | raise CC3200Error(f"Erasing file failed: 0x{s.value:02x}") 1097 | 1098 | def write_file(self, local_file, cc_filename, file_id=-1, sign_file=None, size=0, commit_flag=False, use_api=True): 1099 | # size must be known in advance, so read the whole thing 1100 | file_data = local_file.read() 1101 | file_len = len(file_data) 1102 | 1103 | if not file_len: 1104 | log.warn("Won't upload empty file") 1105 | return 1106 | 1107 | sign_data = None 1108 | fs_flags = None 1109 | 1110 | if commit_flag: 1111 | fs_flags = SLFS_FILE_OPEN_FLAG_COMMIT 1112 | 1113 | if sign_file: 1114 | sign_data = sign_file.read(256) 1115 | fs_flags = ( 1116 | SLFS_FILE_OPEN_FLAG_COMMIT | 1117 | SLFS_FILE_OPEN_FLAG_SECURE | 1118 | SLFS_FILE_PUBLIC_WRITE) 1119 | 1120 | if use_api == False: 1121 | return self._write_file_raw(local_file, cc_filename, file_id, sign_data, fs_flags, size, file_data, file_len) 1122 | else: 1123 | return self._write_file_api(local_file, cc_filename, sign_data, fs_flags, size, file_data, file_len) 1124 | 1125 | def _write_file_raw(self, local_file, cc_filename, file_id, sign_data, fs_flags, size, file_data, file_len): 1126 | fat_info = self.get_fat_info(inactive=False, extended=True) 1127 | filefinfo = None 1128 | for file in fat_info.files: 1129 | if file_id == -1: 1130 | if file.fname == cc_filename: 1131 | filefinfo = file 1132 | break 1133 | elif file.index == file_id: 1134 | filefinfo = file 1135 | break 1136 | 1137 | if filefinfo == None: 1138 | log.info("File not found, only overwriting is supported.") 1139 | raise CC3200Error(f"{cc_filename} or id {file_id} not found, but only overwriting is supported.") 1140 | 1141 | #TODO: commit_flag --> Mirror 1142 | alloc_size_effective = alloc_size = max(size, file_len) + self.SFFS_FAT_FILE_HEADER_SIZE 1143 | blocks = int(alloc_size/fat_info.block_size+1) 1144 | if (fs_flags and fs_flags & SLFS_FILE_OPEN_FLAG_COMMIT): 1145 | alloc_size_effective *= 2 1146 | 1147 | if blocks > filefinfo.size_blocks: 1148 | max_size = filefinfo.size_blocks*fat_info.block_size+self.SFFS_FAT_FILE_HEADER_SIZE 1149 | raise CC3200Error(f"{local_file.name} is too big. It should not be bigger that {max_size}bytes") 1150 | 1151 | log.info("Uploading file %s -> %s (%i) [%d, disk=%d]...", 1152 | local_file.name, cc_filename, filefinfo.index, alloc_size, alloc_size_effective) 1153 | 1154 | if filefinfo.header == None or len(filefinfo.header) != self.SFFS_FAT_FILE_HEADER_SIZE: 1155 | raise CC3200Error(f"File header in flash is missing or has the wrong size") 1156 | 1157 | fatfs_offset = filefinfo.start_block*fat_info.block_size 1158 | header = list(filefinfo.header) 1159 | #TODO: Use old filesize, so space stays reserved 1160 | header[2] = (file_len>>16) & 0xFF 1161 | header[1] = (file_len>>8) & 0xFF 1162 | header[0] = (file_len>>0) & 0xFF 1163 | header_new = bytearray(header) 1164 | self._raw_write(fatfs_offset, header_new, storage_id=STORAGE_ID_SFLASH) 1165 | self._raw_write(self.SFFS_FAT_FILE_HEADER_SIZE+fatfs_offset, file_data, storage_id=STORAGE_ID_SFLASH) 1166 | 1167 | def _write_file_api(self, local_file, cc_filename, sign_data, fs_flags, size, file_data, file_len): 1168 | finfo = self._get_file_info(cc_filename) 1169 | if finfo.exists: 1170 | log.info("File exists on target, erasing") 1171 | self.erase_file(cc_filename) 1172 | 1173 | alloc_size_effective = alloc_size = max(size, file_len) 1174 | 1175 | if (fs_flags and fs_flags & SLFS_FILE_OPEN_FLAG_COMMIT): 1176 | alloc_size_effective *= 2 1177 | 1178 | timeout = self.port.timeout 1179 | if (alloc_size_effective > 200000): 1180 | timeout = max(timeout, 5 * ((alloc_size_effective / 200000) + 1)) # empirical value is ~252925 bytes for 5 sec timeout 1181 | 1182 | log.info("Uploading file %s -> %s [%d, disk=%d]...", 1183 | local_file.name, cc_filename, alloc_size, alloc_size_effective) 1184 | 1185 | with self._serial_timeout(timeout): 1186 | self._open_file_for_write(cc_filename, alloc_size, fs_flags) 1187 | 1188 | pos = 0 1189 | while pos < file_len: 1190 | chunk = file_data[pos:pos+SLFS_BLOCK_SIZE] 1191 | command = OPCODE_FILE_CHUNK + struct.pack(">I", pos) 1192 | command += chunk 1193 | self._send_packet(command) 1194 | res = self._get_last_status() 1195 | if not res.is_ok: 1196 | raise CC3200Error(f"writing at pos {pos} failed") 1197 | pos += len(chunk) 1198 | sys.stderr.write('.') 1199 | sys.stderr.flush() 1200 | 1201 | sys.stderr.write("\n") 1202 | log.debug("Closing file ...") 1203 | return self._close_file(sign_data) 1204 | 1205 | def read_file(self, cc_fname, local_file, file_id=-1, inactive=False): 1206 | finfo = self._get_file_info(cc_fname, file_id, inactive) 1207 | if not finfo.exists: 1208 | raise CC3200Error(f"{cc_fname} does not exist on target") 1209 | 1210 | log.info("Reading file %s -> %s", cc_fname, local_file.name) 1211 | 1212 | if not self.port is None and file_id == -1: 1213 | self._open_file_for_read(cc_fname) 1214 | 1215 | pos = 0 1216 | while pos < finfo.size: 1217 | toread = min(finfo.size - pos, SLFS_BLOCK_SIZE) 1218 | command = OPCODE_READ_FILE_CHUNK + struct.pack(">II", pos, toread) 1219 | self._send_packet(command) 1220 | resp = self._read_packet() 1221 | if len(resp) != toread: 1222 | raise CC3200Error("reading chunk failed") 1223 | 1224 | local_file.write(resp) 1225 | pos += toread 1226 | 1227 | self._close_file() 1228 | return 1229 | 1230 | fat_info = self.get_fat_info(inactive=inactive, extended=True) 1231 | filefinfo = None 1232 | for file in fat_info.files: 1233 | if file_id == -1: 1234 | if file.fname == cc_fname: 1235 | filefinfo = file 1236 | break 1237 | elif file.index == file_id: 1238 | filefinfo = file 1239 | break 1240 | 1241 | sinfo = self._get_storage_info(storage_id=STORAGE_ID_SFLASH) 1242 | fatfs_offset = filefinfo.start_block*fat_info.block_size 1243 | data = self._raw_read(self.SFFS_FAT_FILE_HEADER_SIZE+fatfs_offset, filefinfo.size, storage_id=STORAGE_ID_SFLASH, sinfo=sinfo) 1244 | local_file.write(data) 1245 | 1246 | def write_flash(self, image, erase=True): 1247 | data = image.read() 1248 | data_len = len(data) 1249 | if erase: 1250 | count = int(math.ceil(data_len / float(SLFS_BLOCK_SIZE))) 1251 | self._erase_blocks(0, count, storage_id=STORAGE_ID_SFLASH) 1252 | 1253 | self._raw_write(8, data[8:], storage_id=STORAGE_ID_SFLASH) 1254 | self._raw_write(0, data[:8], storage_id=STORAGE_ID_SFLASH) 1255 | 1256 | def read_flash(self, image_file, offset, size, ignore_max_size=False): 1257 | data = self._raw_read(offset, size, storage_id=STORAGE_ID_SFLASH, ignore_max_size=ignore_max_size) 1258 | image_file.write(data) 1259 | 1260 | def get_fat_info(self, inactive=False, extended=False): 1261 | metadata2_offset = self.SFFS_FAT_METADATA2_CC3200_OFFSET 1262 | if self._device == "cc32xx": 1263 | metadata2_offset = self.SFFS_FAT_METADATA2_CC32XX_OFFSET 1264 | 1265 | sinfo = self._get_storage_info(storage_id=STORAGE_ID_SFLASH) 1266 | 1267 | fat_table_bytes = self._raw_read(0, 2 * sinfo.block_size, 1268 | storage_id=STORAGE_ID_SFLASH, 1269 | sinfo=sinfo) 1270 | 1271 | fat_table_bytes1 = fat_table_bytes[:sinfo.block_size] 1272 | fat_table_bytes2 = fat_table_bytes[sinfo.block_size:] 1273 | 1274 | """ 1275 | In SFFS there're 2 entries of FAT, none of which has a fixed primary 1276 | or secondary role. Instead, these entries are written interchangeably, 1277 | with the newest one being marked with a larger 2-byte number, referred 1278 | in this source code as 'fat_commit_revision' (this is a made-up term). 1279 | 1280 | The algorithm is described in detail here: 1281 | http://processors.wiki.ti.com/index.php/CC3100_%26_CC3200_Serial_Flash_Guide#File_appending 1282 | 1283 | It was also noticed that after the successful write to a newer FAT, 1284 | the older one might got overwritten with 0xFF by the CC3200's SFFS 1285 | driver (effectively marking it as invalid), but not always. 1286 | """ 1287 | 1288 | fat_hdr1 = CC3x00SffsHeader(0, fat_table_bytes1, sinfo) 1289 | fat_hdr2 = CC3x00SffsHeader(1, fat_table_bytes2, sinfo) 1290 | 1291 | fat_hdrs = [] 1292 | if fat_hdr1.is_valid: 1293 | fat_hdrs.append(fat_hdr1) 1294 | if fat_hdr2.is_valid: 1295 | fat_hdrs.append(fat_hdr2) 1296 | metadata2_offset += self.SFFS_FAT_PART_OFFSET 1297 | 1298 | meta2 = self._raw_read(metadata2_offset, metadata2_offset + self.SFFS_FAT_METADATA2_LENGTH, 1299 | storage_id=STORAGE_ID_SFLASH, 1300 | sinfo=sinfo) 1301 | 1302 | if len(fat_hdrs) == 0: 1303 | raise CC3200Error("no valid fat tables found") 1304 | 1305 | if len(fat_hdrs) > 1: 1306 | # find the latest 1307 | fat_hdrs.sort(reverse=True, key=lambda e: e.fat_commit_revision) 1308 | 1309 | if inactive: 1310 | if len(fat_hdrs) > 1: 1311 | fat_hdr = fat_hdrs[1] 1312 | else: 1313 | raise CC3200Error("no valid inactive fat table found") 1314 | else: 1315 | fat_hdr = fat_hdrs[0] 1316 | log.info("selected FAT revision: %d (%s)", fat_hdr.fat_commit_revision, inactive and 'inactive' or 'active') 1317 | 1318 | 1319 | fat_info = CC3x00SffsInfo(fat_hdr, sinfo, meta2, self._device) 1320 | 1321 | if extended: 1322 | for file in fat_info.files: 1323 | fatfs_offset = file.start_block*sinfo.block_size 1324 | fileheader = self._raw_read(fatfs_offset, self.SFFS_FAT_FILE_HEADER_SIZE, storage_id=STORAGE_ID_SFLASH, sinfo=sinfo) 1325 | file.read_header(fileheader) 1326 | 1327 | return fat_info 1328 | 1329 | def list_filesystem(self, json_output=False, inactive=False, extended=False): 1330 | fat_info = self.get_fat_info(inactive=inactive, extended=extended) 1331 | fat_info.print_sffs_info(extended) 1332 | if json_output: 1333 | fat_info.print_sffs_info_json() 1334 | 1335 | def read_all_files(self, local_dir, by_file_id=False, all_by_file_id=False, inactive=False, no_verify=False): 1336 | fat_info = self.get_fat_info(inactive=inactive) 1337 | fat_info.print_sffs_info() 1338 | has_error = False 1339 | 1340 | if not os.path.exists(local_dir): 1341 | os.makedirs(local_dir) 1342 | if not os.path.exists(local_dir): 1343 | raise CC3200Error("could not create local directory %s" % local_dir) 1344 | log.info("Created local directory %s" % local_dir) 1345 | 1346 | for f in fat_info.files: 1347 | ccname = f.fname 1348 | if f.fname == '': 1349 | if by_file_id: 1350 | ccname = str(f.index) 1351 | else: 1352 | log.error("Found file without filename, skipping index=%i", f.index) 1353 | has_error = True 1354 | continue 1355 | 1356 | if ccname.startswith('/'): 1357 | ccname = ccname[1:] 1358 | 1359 | target_file = os.path.join(local_dir, ccname) 1360 | if all_by_file_id: 1361 | target_file = os.path.join(local_dir, str(f.index)) 1362 | 1363 | if not os.path.exists(os.path.dirname(target_file)): 1364 | os.makedirs(name=os.path.dirname(target_file)) 1365 | 1366 | try: 1367 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1368 | if all_by_file_id or ( by_file_id and f.fname == '' ): 1369 | self.read_file(ccname, open(target_file, 'w+b', -1), f.index, inactive=inactive) 1370 | if not no_verify: 1371 | self.read_file(ccname, tmpFile, f.index, inactive=inactive) 1372 | else: 1373 | self.read_file(f.fname, open(target_file, 'w+b', -1), inactive=inactive) 1374 | if not no_verify: 1375 | self.read_file(f.fname, tmpFile, inactive=inactive) 1376 | 1377 | if not no_verify: 1378 | tmpFile.seek(0) 1379 | if tmpFile.read() == open(target_file, 'rb').read(): 1380 | log.info("File %s verified" % target_file) 1381 | else: 1382 | log.error("File %s could not be verified" % target_file) 1383 | has_error = True 1384 | except Exception as ex: 1385 | log.error("File %s could not be read, %s" % (f.fname, str (ex))) 1386 | has_error = True 1387 | 1388 | if has_error: 1389 | log.error("One or more files could not be verified or read at all") 1390 | sys.exit(-4) 1391 | 1392 | def write_all_files(self, local_dir, write=True, use_api=True, no_verify=False): 1393 | has_error = False 1394 | for root, dirs, files in os.walk(local_dir): 1395 | for file in files: 1396 | filepath = os.path.join(root, file) 1397 | ccpath = filepath[len(local_dir):] 1398 | ccpath = ccpath.replace("\\", "/") 1399 | if not ccpath.startswith("/"): 1400 | ccpath = "/" + ccpath 1401 | 1402 | if write: 1403 | self.write_file(local_file=open(filepath, 'rb', -1), cc_filename=ccpath, use_api=use_api) 1404 | if not no_verify: 1405 | log.info("Verify written file with second read...") 1406 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1407 | self.read_file(ccpath, tmpFile) 1408 | tmpFile.seek(0) 1409 | if tmpFile.read() == open(filepath, 'rb').read(): 1410 | log.info("File %s verified" % ccpath) 1411 | else: 1412 | log.error("File %s could not be verified" % ccpath) 1413 | has_error = True 1414 | else: 1415 | log.info("Simulation: Would copy local file %s to cc3200 %s" % (filepath, ccpath)) 1416 | if has_error: 1417 | log.error("One or more files could not be verified") 1418 | sys.exit(-4) 1419 | 1420 | 1421 | def split_argv(cmdline_args): 1422 | """Manually split sys.argv into subcommand sections 1423 | 1424 | The first returned element should contain all global options along with 1425 | the first command. Subsequent elements will contain a command each, with 1426 | options applicable for the specific command. This is needed so we can 1427 | specify different --file-size for different write_file commands. 1428 | """ 1429 | args = [] 1430 | have_cmd = False 1431 | for x in cmdline_args: 1432 | if x in subparsers.choices: 1433 | if have_cmd: 1434 | yield args 1435 | args = [] 1436 | have_cmd = True 1437 | args.append(x) 1438 | else: 1439 | args.append(x) 1440 | 1441 | if args: 1442 | yield args 1443 | 1444 | 1445 | def main(): 1446 | commands = [] 1447 | for cmdargs in split_argv(sys.argv[1:]): 1448 | commands.append(parser.parse_args(cmdargs)) 1449 | 1450 | if len(commands) == 0: 1451 | parser.print_help() 1452 | sys.exit(-1) 1453 | 1454 | args = commands[0] 1455 | 1456 | sop2_method = args.sop2 1457 | reset_method = args.reset 1458 | if sop2_method.pin == reset_method.pin and reset_method.pin != 'none': 1459 | log.error("sop2 and reset methods cannot be the same output pin") 1460 | sys.exit(-3) 1461 | 1462 | port_name = args.port 1463 | 1464 | if not args.image_file is None: 1465 | cc = CC3200Connection(None, reset_method, sop2_method, erase_timeout=args.erase_timeout, device=args.device, image_file=args.image_file, output_file=args.output_file) 1466 | 1467 | else: 1468 | try: 1469 | p = serial.Serial( 1470 | port_name, baudrate=CC3200_BAUD, parity=serial.PARITY_NONE, 1471 | stopbits=serial.STOPBITS_ONE) 1472 | except (Exception, ) as e: 1473 | log.warn("unable to open serial port %s: %s", port_name, e) 1474 | sys.exit(-2) 1475 | 1476 | cc = CC3200Connection(p, reset_method, sop2_method, erase_timeout=args.erase_timeout) 1477 | try: 1478 | cc.connect() 1479 | log.info("connected to target") 1480 | except (Exception, ) as e: 1481 | log.error(f"Could not connect to target: {e}") 1482 | sys.exit(-3) 1483 | 1484 | log.info("Version: %s", cc.vinfo) 1485 | 1486 | # TODO: sane error handling 1487 | 1488 | if cc.vinfo.is_cc3200: 1489 | log.info("This is a CC3200 device") 1490 | cc.switch_to_nwp_bootloader() 1491 | log.info("APPS version: %s", cc.vinfo_apps) 1492 | 1493 | check_fat = False 1494 | 1495 | for command in commands: 1496 | if command.cmd == "format_flash": 1497 | cc.format_slfs(command.size) 1498 | 1499 | if command.cmd == 'write_file': 1500 | use_api = True 1501 | if not command.image_file is None and not command.output_file is None: 1502 | use_api = False 1503 | cc.copy_input_file_to_output_file() 1504 | 1505 | cc.write_file(command.local_file, command.cc_filename, command.file_id, 1506 | command.signature, command.file_size, 1507 | command.commit_flag, use_api) 1508 | 1509 | if not command.no_verify: 1510 | log.info("Read file after writing for verification...") 1511 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1512 | cc.read_file(command.cc_filename, tmpFile) 1513 | tmpFile.seek(0) 1514 | command.local_file.seek(0) 1515 | if tmpFile.read() == command.local_file.read(): 1516 | log.info("File %s verified" % command.cc_filename) 1517 | else: 1518 | log.error("File %s could not be verified" % command.cc_filename) 1519 | sys.exit(-4) 1520 | 1521 | check_fat = True 1522 | 1523 | if command.cmd == "read_file": 1524 | cc.read_file(command.cc_filename, command.local_file, command.file_id, command.inactive) 1525 | if not command.no_verify: 1526 | log.info("Read file a second time for verification...") 1527 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1528 | cc.read_file(command.cc_filename, tmpFile, command.file_id, command.inactive) 1529 | tmpFile.seek(0) 1530 | command.local_file.seek(0) 1531 | if tmpFile.read() == command.local_file.read(): 1532 | log.info("File %s verified" % command.cc_filename) 1533 | else: 1534 | log.error("File %s could not be verified" % command.cc_filename) 1535 | local_file_name = command.local_file.name 1536 | command.local_file.close() 1537 | os.remove(local_file_name) 1538 | sys.exit(-4) 1539 | 1540 | if command.cmd == "erase_file": 1541 | log.info("Erasing file %s", command.filename) 1542 | cc.erase_file(command.filename) 1543 | 1544 | if command.cmd == "write_flash": 1545 | cc.write_flash(command.gang_image_file, not command.no_erase) 1546 | if not command.no_verify: 1547 | log.info("Verify flash write by reading...") 1548 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1549 | cc.read_flash(tmpFile, 0, -1) 1550 | tmpFile.seek(0) 1551 | command.gang_image_file.seek(0) 1552 | if tmpFile.read() == command.gang_image_file.read(): 1553 | log.info("Flash verified") 1554 | else: 1555 | log.error("Flash could not be verified, please flash again!") 1556 | sys.exit(-4) 1557 | 1558 | 1559 | if command.cmd == "read_flash": 1560 | cc.read_flash(command.dump_file, command.offset, command.size, command.ignore_max_size) 1561 | if not command.no_verify: 1562 | log.info("Verify flash dump with second reading...") 1563 | tmpFile = tempfile.NamedTemporaryFile(mode='w+b') 1564 | cc.read_flash(tmpFile, command.offset, command.size, command.ignore_max_size) 1565 | tmpFile.seek(0) 1566 | command.dump_file.seek(0) 1567 | if tmpFile.read() == command.dump_file.read(): 1568 | log.info("Flash verified, reading equal!") 1569 | else: 1570 | log.error("Flash could not be verified, first and second dump are different!") 1571 | dump_file_name = command.dump_file.name 1572 | command.dump_file.close() 1573 | os.remove(dump_file_name) 1574 | sys.exit(-4) 1575 | 1576 | if command.cmd == "list_filesystem": 1577 | cc.list_filesystem(command.json_output, command.inactive, command.extended) 1578 | 1579 | if command.cmd == "read_all_files": 1580 | cc.read_all_files(command.local_dir, command.by_file_id, command.all_by_file_id, command.inactive, command.no_verify) 1581 | 1582 | if command.cmd == "write_all_files": 1583 | use_api = True 1584 | if not command.image_file is None and not command.output_file is None: 1585 | use_api = False 1586 | cc.copy_input_file_to_output_file() 1587 | cc.write_all_files(command.local_dir, command.simulate, use_api, command.no_verify) 1588 | check_fat = True 1589 | 1590 | if command.cmd == "dll_data_test": 1591 | dll_data('rbtl3100s.dll') 1592 | 1593 | 1594 | if check_fat: 1595 | fat_info = cc.get_fat_info() # check FAT after each write_file operation 1596 | fat_info.print_sffs_info_short() 1597 | 1598 | if args.reboot_to_app: 1599 | cc.reboot_to_app() 1600 | 1601 | log.info("All commands done, bye.") 1602 | 1603 | 1604 | if __name__ == '__main__': 1605 | main() 1606 | -------------------------------------------------------------------------------- /cc3200tool/dll/rbtl3100.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/cc3200tool/12f26f21abf3c595890d181e965135c32cf759e0/cc3200tool/dll/rbtl3100.dll -------------------------------------------------------------------------------- /cc3200tool/dll/rbtl3100s.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/cc3200tool/12f26f21abf3c595890d181e965135c32cf759e0/cc3200tool/dll/rbtl3100s.dll -------------------------------------------------------------------------------- /cc3200tool/dll/rbtl3101.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/cc3200tool/12f26f21abf3c595890d181e965135c32cf759e0/cc3200tool/dll/rbtl3101.dll -------------------------------------------------------------------------------- /cc3200tool/dll/rbtl3101_121.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/cc3200tool/12f26f21abf3c595890d181e965135c32cf759e0/cc3200tool/dll/rbtl3101_121.dll -------------------------------------------------------------------------------- /cc3200tool/dll/rbtl3101_132.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/cc3200tool/12f26f21abf3c595890d181e965135c32cf759e0/cc3200tool/dll/rbtl3101_132.dll -------------------------------------------------------------------------------- /scripts/cc3200tool: -------------------------------------------------------------------------------- 1 | #!python 2 | # 3 | # cc3200tool - work with TI's CC3200 SimpleLink (TM) filesystem. 4 | # Copyright (C) 2016-2020 Allterco Robotics 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | 21 | from cc3200tool.cc import main 22 | main() 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name="cc3200tool", 5 | version="1.2.4", 6 | description="A tool to down-/upload files form/to TI CC3200", 7 | author="Kiril Zyapkov, 0xbadbee", 8 | author_email="k.zyapkov@allterco.com", 9 | url="https://github.com/toniebox-reverse-engineering/cc3200tool", 10 | packages=['cc3200tool'], 11 | package_data={'cc3200tool': ['dll/*.dll']}, 12 | entry_points = { 13 | 'console_scripts': ['cc3200tool=cc3200tool.cc:main'], 14 | }, 15 | install_requires=['pyserial'], 16 | ) 17 | --------------------------------------------------------------------------------