aboutsummaryrefslogtreecommitdiff
path: root/tools/pydfu.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/pydfu.py')
-rwxr-xr-xtools/pydfu.py300
1 files changed, 149 insertions, 151 deletions
diff --git a/tools/pydfu.py b/tools/pydfu.py
index 962ba2b7f..9f345d016 100755
--- a/tools/pydfu.py
+++ b/tools/pydfu.py
@@ -25,34 +25,34 @@ import zlib
# VID/PID
__VID = 0x0483
-__PID = 0xdf11
+__PID = 0xDF11
# USB request __TIMEOUT
__TIMEOUT = 4000
# DFU commands
-__DFU_DETACH = 0
-__DFU_DNLOAD = 1
-__DFU_UPLOAD = 2
+__DFU_DETACH = 0
+__DFU_DNLOAD = 1
+__DFU_UPLOAD = 2
__DFU_GETSTATUS = 3
__DFU_CLRSTATUS = 4
-__DFU_GETSTATE = 5
-__DFU_ABORT = 6
+__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_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_DESCRIPTOR_TYPE = 0x21
# USB device handle
@@ -68,12 +68,14 @@ __DFU_INTERFACE = 0
# Python 3 deprecated getargspec in favour of getfullargspec, but
# Python 2 doesn't have the latter, so detect which one to use
-getargspec = getattr(inspect, 'getfullargspec', inspect.getargspec)
+getargspec = getattr(inspect, "getfullargspec", inspect.getargspec)
-if 'length' in getargspec(usb.util.get_string).args:
+if "length" in getargspec(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):
@@ -83,17 +85,17 @@ else:
def find_dfu_cfg_descr(descr):
if len(descr) == 9 and descr[0] == 9 and descr[1] == _DFU_DESCRIPTOR_TYPE:
nt = collections.namedtuple(
- 'CfgDescr',
+ "CfgDescr",
[
- 'bLength',
- 'bDescriptorType',
- 'bmAttributes',
- 'wDetachTimeOut',
- 'wTransferSize',
- 'bcdDFUVersion'
- ]
+ "bLength",
+ "bDescriptorType",
+ "bmAttributes",
+ "wDetachTimeOut",
+ "wTransferSize",
+ "bcdDFUVersion",
+ ],
)
- return nt(*struct.unpack('<BBBHHH', bytearray(descr)))
+ return nt(*struct.unpack("<BBBHHH", bytearray(descr)))
return None
@@ -102,9 +104,9 @@ def init():
global __dev, __cfg_descr
devices = get_dfu_devices(idVendor=__VID, idProduct=__PID)
if not devices:
- raise ValueError('No DFU device found')
+ raise ValueError("No DFU device found")
if len(devices) > 1:
- raise ValueError('Multiple DFU devices found')
+ raise ValueError("Multiple DFU devices found")
__dev = devices[0]
__dev.set_configuration()
@@ -127,8 +129,7 @@ def init():
status = get_status()
if status == __DFU_STATE_DFU_IDLE:
break
- elif (status == __DFU_STATE_DFU_DOWNLOAD_IDLE
- or status == __DFU_STATE_DFU_UPLOAD_IDLE):
+ elif status == __DFU_STATE_DFU_DOWNLOAD_IDLE or status == __DFU_STATE_DFU_UPLOAD_IDLE:
abort_request()
else:
clr_status()
@@ -141,64 +142,61 @@ def abort_request():
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)
+ __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)
+ stat = __dev.ctrl_transfer(0xA1, __DFU_GETSTATUS, 0, __DFU_INTERFACE, 6, 20000)
return stat[4]
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)
+ __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, "\x41", __TIMEOUT)
# Execute last command
if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY:
- raise Exception('DFU: erase failed')
+ raise Exception("DFU: erase failed")
# Check command state
if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE:
- raise Exception('DFU: erase failed')
+ raise Exception("DFU: erase failed")
def page_erase(addr):
"""Erases a single page."""
if __verbose:
- print('Erasing page: 0x%x...' % (addr))
+ print("Erasing page: 0x%x..." % (addr))
# Send DNLOAD with first byte=0x41 and page address
- buf = struct.pack('<BI', 0x41, addr)
+ buf = struct.pack("<BI", 0x41, addr)
__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT)
# Execute last command
if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY:
- raise Exception('DFU: erase failed')
+ raise Exception("DFU: erase failed")
# Check command state
if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE:
- raise Exception('DFU: erase failed')
+ raise Exception("DFU: erase failed")
def set_address(addr):
"""Sets the address for the next operation."""
# Send DNLOAD with first byte=0x21 and page address
- buf = struct.pack('<BI', 0x21, addr)
+ buf = struct.pack("<BI", 0x21, addr)
__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT)
# Execute last command
if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY:
- raise Exception('DFU: set address failed')
+ raise Exception("DFU: set address failed")
# Check command state
if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE:
- raise Exception('DFU: set address failed')
+ raise Exception("DFU: set address failed")
def write_memory(addr, buf, progress=None, progress_addr=0, progress_size=0):
@@ -213,28 +211,29 @@ def write_memory(addr, buf, progress=None, progress_addr=0, progress_size=0):
while xfer_bytes < xfer_total:
if __verbose and xfer_count % 512 == 0:
- print('Addr 0x%x %dKBs/%dKBs...' % (xfer_base + xfer_bytes,
- xfer_bytes // 1024,
- xfer_total // 1024))
+ print(
+ "Addr 0x%x %dKBs/%dKBs..."
+ % (xfer_base + xfer_bytes, xfer_bytes // 1024, xfer_total // 1024)
+ )
if progress and xfer_count % 2 == 0:
- progress(progress_addr, xfer_base + xfer_bytes - progress_addr,
- progress_size)
+ progress(progress_addr, xfer_base + xfer_bytes - progress_addr, progress_size)
# Set mem write address
set_address(xfer_base + xfer_bytes)
# Send DNLOAD with fw data
chunk = min(__cfg_descr.wTransferSize, xfer_total - xfer_bytes)
- __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 2, __DFU_INTERFACE,
- buf[xfer_bytes:xfer_bytes + chunk], __TIMEOUT)
+ __dev.ctrl_transfer(
+ 0x21, __DFU_DNLOAD, 2, __DFU_INTERFACE, buf[xfer_bytes : xfer_bytes + chunk], __TIMEOUT
+ )
# Execute last command
if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY:
- raise Exception('DFU: write memory failed')
+ raise Exception("DFU: write memory failed")
# Check command state
if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE:
- raise Exception('DFU: write memory failed')
+ raise Exception("DFU: write memory failed")
xfer_count += 1
xfer_bytes += chunk
@@ -255,14 +254,14 @@ def write_page(buf, xfer_offset):
# Execute last command
if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY:
- raise Exception('DFU: write memory failed')
+ raise Exception("DFU: write memory failed")
# Check command state
if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE:
- raise Exception('DFU: write memory failed')
+ raise Exception("DFU: write memory failed")
if __verbose:
- print('Write: 0x%x ' % (xfer_base + xfer_offset))
+ print("Write: 0x%x " % (xfer_base + xfer_offset))
def exit_dfu():
@@ -271,13 +270,12 @@ def exit_dfu():
set_address(0x08000000)
# Send DNLOAD with 0 length to exit DFU
- __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE,
- None, __TIMEOUT)
+ __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, None, __TIMEOUT)
try:
# Execute last command
if get_status() != __DFU_STATE_DFU_MANIFEST:
- print('Failed to reset device')
+ print("Failed to reset device")
# Release device
usb.util.dispose_resources(__dev)
@@ -301,7 +299,7 @@ def consume(fmt, data, names):
def cstring(string):
"""Extracts a null-terminated string from a byte array."""
- return string.decode('utf-8').split('\0', 1)[0]
+ return string.decode("utf-8").split("\0", 1)[0]
def compute_crc(data):
@@ -320,8 +318,8 @@ def read_dfu_file(filename):
If an error occurs while parsing the file, then None is returned.
"""
- print('File: {}'.format(filename))
- with open(filename, 'rb') as fin:
+ print("File: {}".format(filename))
+ with open(filename, "rb") as fin:
data = fin.read()
crc = compute_crc(data[:-4])
elements = []
@@ -334,11 +332,12 @@ def read_dfu_file(filename):
# B uint8_t version 1
# I uint32_t size Size of the DFU file (without suffix)
# B uint8_t targets Number of targets
- dfu_prefix, data = consume('<5sBIB', data,
- 'signature version size targets')
- print(' %(signature)s v%(version)d, image size: %(size)d, '
- 'targets: %(targets)d' % dfu_prefix)
- for target_idx in range(dfu_prefix['targets']):
+ dfu_prefix, data = consume("<5sBIB", data, "signature version size targets")
+ print(
+ " %(signature)s v%(version)d, image size: %(size)d, "
+ "targets: %(targets)d" % dfu_prefix
+ )
+ for target_idx in range(dfu_prefix["targets"]):
# Decode the Image Prefix
#
# <6sBI255s2I
@@ -349,40 +348,40 @@ def read_dfu_file(filename):
# 255s char[255] name Name of the target
# I uint32_t size Size of image (without prefix)
# I uint32_t elements Number of elements in the image
- img_prefix, data = consume('<6sBI255s2I', data,
- 'signature altsetting named name '
- 'size elements')
- img_prefix['num'] = target_idx
- if img_prefix['named']:
- img_prefix['name'] = cstring(img_prefix['name'])
+ img_prefix, data = consume(
+ "<6sBI255s2I", data, "signature altsetting named name " "size elements"
+ )
+ img_prefix["num"] = target_idx
+ if img_prefix["named"]:
+ img_prefix["name"] = cstring(img_prefix["name"])
else:
- img_prefix['name'] = ''
- print(' %(signature)s %(num)d, alt setting: %(altsetting)s, '
- 'name: "%(name)s", size: %(size)d, elements: %(elements)d'
- % img_prefix)
+ img_prefix["name"] = ""
+ print(
+ " %(signature)s %(num)d, alt setting: %(altsetting)s, "
+ 'name: "%(name)s", size: %(size)d, elements: %(elements)d' % img_prefix
+ )
- target_size = img_prefix['size']
+ target_size = img_prefix["size"]
target_data = data[:target_size]
data = data[target_size:]
- for elem_idx in range(img_prefix['elements']):
+ for elem_idx in range(img_prefix["elements"]):
# Decode target prefix
#
# <2I
# < little endian Endianness
# I uint32_t element Address
# I uint32_t element Size
- elem_prefix, target_data = consume('<2I', target_data, 'addr size')
- elem_prefix['num'] = elem_idx
- print(' %(num)d, address: 0x%(addr)08x, size: %(size)d'
- % elem_prefix)
- elem_size = elem_prefix['size']
+ elem_prefix, target_data = consume("<2I", target_data, "addr size")
+ elem_prefix["num"] = elem_idx
+ print(" %(num)d, address: 0x%(addr)08x, size: %(size)d" % elem_prefix)
+ elem_size = elem_prefix["size"]
elem_data = target_data[:elem_size]
target_data = target_data[elem_size:]
- elem_prefix['data'] = elem_data
+ elem_prefix["data"] = elem_data
elements.append(elem_prefix)
if len(target_data):
- print('target %d PARSE ERROR' % target_idx)
+ print("target %d PARSE ERROR" % target_idx)
# Decode DFU Suffix
#
@@ -395,16 +394,19 @@ def read_dfu_file(filename):
# 3s char[3] ufd "UFD"
# B uint8_t len 16
# I uint32_t crc32 Checksum
- dfu_suffix = named(struct.unpack('<4H3sBI', data[:16]),
- 'device product vendor dfu ufd len crc')
- print(' usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, '
- 'dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x' % dfu_suffix)
- if crc != dfu_suffix['crc']:
- print('CRC ERROR: computed crc32 is 0x%08x' % crc)
+ dfu_suffix = named(
+ struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc"
+ )
+ print(
+ " usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, "
+ "dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix
+ )
+ if crc != dfu_suffix["crc"]:
+ print("CRC ERROR: computed crc32 is 0x%08x" % crc)
return
data = data[16:]
if data:
- print('PARSE ERROR')
+ print("PARSE ERROR")
return
return elements
@@ -418,8 +420,7 @@ class FilterDFU(object):
def __call__(self, device):
for cfg in device:
for intf in cfg:
- return (intf.bInterfaceClass == 0xFE and
- intf.bInterfaceSubClass == 1)
+ return intf.bInterfaceClass == 0xFE and intf.bInterfaceSubClass == 1
def get_dfu_devices(*args, **kwargs):
@@ -429,8 +430,7 @@ def get_dfu_devices(*args, **kwargs):
"""
# Convert to list for compatibility with newer PyUSB
- return list(usb.core.find(*args, find_all=True,
- custom_match=FilterDFU(), **kwargs))
+ return list(usb.core.find(*args, find_all=True, custom_match=FilterDFU(), **kwargs))
def get_memory_layout(device):
@@ -446,25 +446,29 @@ def get_memory_layout(device):
cfg = device[0]
intf = cfg[(0, 0)]
mem_layout_str = get_string(device, intf.iInterface)
- mem_layout = mem_layout_str.split('/')
+ mem_layout = mem_layout_str.split("/")
result = []
for mem_layout_index in range(1, len(mem_layout), 2):
addr = int(mem_layout[mem_layout_index], 0)
- segments = mem_layout[mem_layout_index + 1].split(',')
- seg_re = re.compile(r'(\d+)\*(\d+)(.)(.)')
+ segments = mem_layout[mem_layout_index + 1].split(",")
+ seg_re = re.compile(r"(\d+)\*(\d+)(.)(.)")
for segment in segments:
seg_match = seg_re.match(segment)
num_pages = int(seg_match.groups()[0], 10)
page_size = int(seg_match.groups()[1], 10)
multiplier = seg_match.groups()[2]
- if multiplier == 'K':
+ if multiplier == "K":
page_size *= 1024
- if multiplier == 'M':
+ if multiplier == "M":
page_size *= 1024 * 1024
size = num_pages * page_size
last_addr = addr + size - 1
- result.append(named((addr, last_addr, size, num_pages, page_size),
- 'addr last_addr size num_pages page_size'))
+ result.append(
+ named(
+ (addr, last_addr, size, num_pages, page_size),
+ "addr last_addr size num_pages page_size",
+ )
+ )
addr += size
return result
@@ -473,18 +477,22 @@ def list_dfu_devices(*args, **kwargs):
"""Prints a lits of devices detected in DFU mode."""
devices = get_dfu_devices(*args, **kwargs)
if not devices:
- print('No DFU capable devices found')
+ print("No DFU capable devices found")
return
for device in devices:
- print('Bus {} Device {:03d}: ID {:04x}:{:04x}'
- .format(device.bus, device.address,
- device.idVendor, device.idProduct))
+ print(
+ "Bus {} Device {:03d}: ID {:04x}:{:04x}".format(
+ device.bus, device.address, device.idVendor, device.idProduct
+ )
+ )
layout = get_memory_layout(device)
- print('Memory Layout')
+ print("Memory Layout")
for entry in layout:
- print(' 0x{:x} {:2d} pages of {:3d}K bytes'
- .format(entry['addr'], entry['num_pages'],
- entry['page_size'] // 1024))
+ print(
+ " 0x{:x} {:2d} pages of {:3d}K bytes".format(
+ entry["addr"], entry["num_pages"], entry["page_size"] // 1024
+ )
+ )
def write_elements(elements, mass_erase_used, progress=None):
@@ -494,9 +502,9 @@ def write_elements(elements, mass_erase_used, progress=None):
mem_layout = get_memory_layout(__dev)
for elem in elements:
- addr = elem['addr']
- size = elem['size']
- data = elem['data']
+ addr = elem["addr"]
+ size = elem["size"]
+ data = elem["data"]
elem_size = size
elem_addr = addr
if progress:
@@ -505,18 +513,16 @@ def write_elements(elements, mass_erase_used, progress=None):
write_size = size
if not mass_erase_used:
for segment in mem_layout:
- if addr >= segment['addr'] and \
- addr <= segment['last_addr']:
+ 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_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)
+ write_memory(addr, data[:write_size], progress, elem_addr, elem_size)
data = data[write_size:]
addr += write_size
size -= write_size
@@ -528,45 +534,36 @@ 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='')
+ 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
+ pass # Ignore Windows CLI "WinError 87" on Python 3.6
if offset == size:
- print('')
+ print("")
def main():
"""Test program for verifying this files functionality."""
global __verbose
# Parse CMD args
- parser = argparse.ArgumentParser(description='DFU Python Util')
+ parser = argparse.ArgumentParser(description="DFU Python Util")
parser.add_argument(
- '-l', '--list',
- help='list available DFU devices',
- action='store_true',
- default=False
+ "-l", "--list", help="list available DFU devices", action="store_true", default=False
)
parser.add_argument(
- '-m', '--mass-erase',
- help='mass erase device',
- action='store_true',
- default=False
+ "-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
+ "-u", "--upload", help="read file from DFU device", dest="path", default=False
)
parser.add_argument(
- '-v', '--verbose',
- help='increase output verbosity',
- action='store_true',
- default=False
+ "-v", "--verbose", help="increase output verbosity", action="store_true", default=False
)
args = parser.parse_args()
@@ -579,21 +576,22 @@ def main():
init()
if args.mass_erase:
- print('Mass erase...')
+ print("Mass erase...")
mass_erase()
if args.path:
elements = read_dfu_file(args.path)
if not elements:
return
- print('Writing memory...')
+ print("Writing memory...")
write_elements(elements, args.mass_erase, progress=cli_progress)
- print('Exiting DFU...')
+ print("Exiting DFU...")
exit_dfu()
return
- print('No command specified')
+ print("No command specified")
+
-if __name__ == '__main__':
+if __name__ == "__main__":
main()