From 4fd287655a72b9aea14cdac715ad5b90ed082ed2 Mon Sep 17 00:00:00 2001 From: Raghuram Subramani Date: Sun, 19 Jun 2022 19:47:51 +0530 Subject: add circuitpython code --- circuitpython/tools/pydfu.py | 626 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 626 insertions(+) create mode 100755 circuitpython/tools/pydfu.py (limited to 'circuitpython/tools/pydfu.py') diff --git a/circuitpython/tools/pydfu.py b/circuitpython/tools/pydfu.py new file mode 100755 index 0000000..ce34b08 --- /dev/null +++ b/circuitpython/tools/pydfu.py @@ -0,0 +1,626 @@ +#!/usr/bin/env python + +# SPDX-FileCopyrightText: Copyright (c) 2013/2014 Ibrahim Abdelkader +# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +# +# SPDX-License-Identifier: MIT + +"""This module implements enough functionality to program the STM32F4xx over +DFU, without requiring dfu-util. + +See app note AN3156 for a description of the DFU protocol. +See document UM0391 for a dscription of the DFuse file. +""" + +from __future__ import print_function + +import argparse +import collections +import inspect +import re +import struct +import sys +import usb.core +import usb.util +import zlib + +# USB request __TIMEOUT +__TIMEOUT = 4000 + +# DFU commands +__DFU_DETACH = 0 +__DFU_DNLOAD = 1 +__DFU_UPLOAD = 2 +__DFU_GETSTATUS = 3 +__DFU_CLRSTATUS = 4 +__DFU_GETSTATE = 5 +__DFU_ABORT = 6 + +# DFU status +__DFU_STATE_APP_IDLE = 0x00 +__DFU_STATE_APP_DETACH = 0x01 +__DFU_STATE_DFU_IDLE = 0x02 +__DFU_STATE_DFU_DOWNLOAD_SYNC = 0x03 +__DFU_STATE_DFU_DOWNLOAD_BUSY = 0x04 +__DFU_STATE_DFU_DOWNLOAD_IDLE = 0x05 +__DFU_STATE_DFU_MANIFEST_SYNC = 0x06 +__DFU_STATE_DFU_MANIFEST = 0x07 +__DFU_STATE_DFU_MANIFEST_WAIT_RESET = 0x08 +__DFU_STATE_DFU_UPLOAD_IDLE = 0x09 +__DFU_STATE_DFU_ERROR = 0x0A + +_DFU_DESCRIPTOR_TYPE = 0x21 + +__DFU_STATUS_STR = { + __DFU_STATE_APP_IDLE: "STATE_APP_IDLE", + __DFU_STATE_APP_DETACH: "STATE_APP_DETACH", + __DFU_STATE_DFU_IDLE: "STATE_DFU_IDLE", + __DFU_STATE_DFU_DOWNLOAD_SYNC: "STATE_DFU_DOWNLOAD_SYNC", + __DFU_STATE_DFU_DOWNLOAD_BUSY: "STATE_DFU_DOWNLOAD_BUSY", + __DFU_STATE_DFU_DOWNLOAD_IDLE: "STATE_DFU_DOWNLOAD_IDLE", + __DFU_STATE_DFU_MANIFEST_SYNC: "STATE_DFU_MANIFEST_SYNC", + __DFU_STATE_DFU_MANIFEST: "STATE_DFU_MANIFEST", + __DFU_STATE_DFU_MANIFEST_WAIT_RESET: "STATE_DFU_MANIFEST_WAIT_RESET", + __DFU_STATE_DFU_UPLOAD_IDLE: "STATE_DFU_UPLOAD_IDLE", + __DFU_STATE_DFU_ERROR: "STATE_DFU_ERROR", +} + +# USB device handle +__dev = None + +# Configuration descriptor of the device +__cfg_descr = None + +__verbose = None + +# USB DFU interface +__DFU_INTERFACE = 0 + +import inspect + +if "length" in inspect.getfullargspec(usb.util.get_string).args: + # PyUSB 1.0.0.b1 has the length argument + def get_string(dev, index): + return usb.util.get_string(dev, 255, index) + +else: + # PyUSB 1.0.0.b2 dropped the length argument + def get_string(dev, index): + return usb.util.get_string(dev, index) + + +def find_dfu_cfg_descr(descr): + if len(descr) == 9 and descr[0] == 9 and descr[1] == _DFU_DESCRIPTOR_TYPE: + nt = collections.namedtuple( + "CfgDescr", + [ + "bLength", + "bDescriptorType", + "bmAttributes", + "wDetachTimeOut", + "wTransferSize", + "bcdDFUVersion", + ], + ) + return nt(*struct.unpack(" 1: + raise ValueError("Multiple DFU devices found") + __dev = devices[0] + __dev.set_configuration() + + # Claim DFU interface + usb.util.claim_interface(__dev, __DFU_INTERFACE) + + # Find the DFU configuration descriptor, either in the device or interfaces + __cfg_descr = None + for cfg in __dev.configurations(): + __cfg_descr = find_dfu_cfg_descr(cfg.extra_descriptors) + if __cfg_descr: + break + for itf in cfg.interfaces(): + __cfg_descr = find_dfu_cfg_descr(itf.extra_descriptors) + if __cfg_descr: + break + + # Get device into idle state + for attempt in range(4): + status = get_status() + if status == __DFU_STATE_DFU_IDLE: + break + elif status == __DFU_STATE_DFU_DOWNLOAD_IDLE or status == __DFU_STATE_DFU_UPLOAD_IDLE: + abort_request() + else: + clr_status() + + +def abort_request(): + """Sends an abort request.""" + __dev.ctrl_transfer(0x21, __DFU_ABORT, 0, __DFU_INTERFACE, None, __TIMEOUT) + + +def clr_status(): + """Clears any error status (perhaps left over from a previous session).""" + __dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE, None, __TIMEOUT) + + +def get_status(): + """Get the status of the last operation.""" + stat = __dev.ctrl_transfer(0xA1, __DFU_GETSTATUS, 0, __DFU_INTERFACE, 6, 20000) + + # firmware can provide an optional string for any error + if stat[5]: + message = get_string(__dev, stat[5]) + if message: + print(message) + + return stat[4] + + +def check_status(stage, expected): + status = get_status() + if status != expected: + raise SystemExit("DFU: %s failed (%s)" % (stage, __DFU_STATUS_STR.get(status, status))) + + +def mass_erase(): + """Performs a MASS erase (i.e. erases the entire device).""" + # Send DNLOAD with first byte=0x41 + __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, "\x41", __TIMEOUT) + + # Execute last command + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_BUSY) + + # Check command state + check_status("erase", __DFU_STATE_DFU_DOWNLOAD_IDLE) + + +def page_erase(addr): + """Erases a single page.""" + if __verbose: + print("Erasing page: 0x%x..." % (addr)) + + # Send DNLOAD with first byte=0x41 and page address + buf = struct.pack(" 0: + write_size = size + if not mass_erase_used: + for segment in mem_layout: + if addr >= segment["addr"] and addr <= segment["last_addr"]: + # We found the page containing the address we want to + # write, erase it + page_size = segment["page_size"] + page_addr = addr & ~(page_size - 1) + if addr + write_size > page_addr + page_size: + write_size = page_addr + page_size - addr + page_erase(page_addr) + break + write_memory(addr, data[:write_size], progress, elem_addr, elem_size) + data = data[write_size:] + addr += write_size + size -= write_size + if progress: + progress(elem_addr, addr - elem_addr, elem_size) + + +def cli_progress(addr, offset, size): + """Prints a progress report suitable for use on the command line.""" + width = 25 + done = offset * width // size + print( + "\r0x{:08x} {:7d} [{}{}] {:3d}% ".format( + addr, size, "=" * done, " " * (width - done), offset * 100 // size + ), + end="", + ) + try: + sys.stdout.flush() + except OSError: + pass # Ignore Windows CLI "WinError 87" on Python 3.6 + if offset == size: + print("") + + +def main(): + """Test program for verifying this files functionality.""" + global __verbose + # Parse CMD args + parser = argparse.ArgumentParser(description="DFU Python Util") + parser.add_argument( + "-l", "--list", help="list available DFU devices", action="store_true", default=False + ) + parser.add_argument("--vid", help="USB Vendor ID", type=lambda x: int(x, 0), default=None) + parser.add_argument("--pid", help="USB Product ID", type=lambda x: int(x, 0), default=None) + parser.add_argument( + "-m", "--mass-erase", help="mass erase device", action="store_true", default=False + ) + parser.add_argument( + "-u", "--upload", help="read file from DFU device", dest="path", default=False + ) + parser.add_argument("-x", "--exit", help="Exit DFU", action="store_true", default=False) + parser.add_argument( + "-v", "--verbose", help="increase output verbosity", action="store_true", default=False + ) + args = parser.parse_args() + + __verbose = args.verbose + + kwargs = {} + if args.vid: + kwargs["idVendor"] = args.vid + + if args.pid: + kwargs["idProduct"] = args.pid + + if args.list: + list_dfu_devices(**kwargs) + return + + init(**kwargs) + + command_run = False + if args.mass_erase: + print("Mass erase...") + mass_erase() + command_run = True + + if args.path: + elements = read_dfu_file(args.path) + if not elements: + print("No data in dfu file") + return + print("Writing memory...") + write_elements(elements, args.mass_erase, progress=cli_progress) + + print("Exiting DFU...") + exit_dfu() + command_run = True + + if args.exit: + print("Exiting DFU...") + exit_dfu() + command_run = True + + if command_run: + print("Finished") + else: + print("No command specified") + + +if __name__ == "__main__": + main() -- cgit v1.2.3