Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 537 lines (428 sloc) 16.1 KB
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Copyright 2010-2012 Michael Ossmann, Travis Goodspeed
#
# This file is forked from Project Ubertooth in order to support the
# STM32F2xx.
# The STM32 series of ARM chips implement a weird variant of DFU, so
# I've written this client in order to understand it. Sometime in the
# future, I expect to build a general-purpose DFU tool that works for
# all chips. For now, though, this will certainly not be expected to
# run on anything but the STM32 series.
# http://pyusb.sourceforge.net/docs/1.0/tutorial.html
from __future__ import print_function
import struct
import sys
import time
import usb.core
stm32_vendor = 0x0483
stm32_product = 0xdf11
ram_offset = 0x20002000 # RAM
ram_size = 0x1E000
application_offset = 0x08000000 # Flash
application_size = 0x100000 # 1MB
rom_offset = 0x1fff0000 # ROM
rom_size = 0x8000 # 32K
otp_offset = 0x1fff7800 # OTP ROM
otp_size = 512
# Smaller block sizes cause problems. Not sure why.
block_size = 2048
class Enumeration(object):
def __init__(self, id, name):
self._id = id
self._name = name
setattr(self.__class__, name, self)
self.map[id] = self
def __int__(self):
return self.id
def __repr__(self):
return self.name
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@classmethod
def create_from_map(cls):
for id, name in cls.map.items():
cls(id, name)
class Request(Enumeration):
map = {
0: 'DETACH',
1: 'DNLOAD',
2: 'UPLOAD',
3: 'GETSTATUS',
4: 'CLRSTATUS',
5: 'GETSTATE',
6: 'ABORT',
}
Request.create_from_map()
class State(Enumeration):
map = {
0: 'appIDLE',
1: 'appDETACH',
2: 'dfuIDLE',
3: 'dfuDNLOAD_SYNC',
4: 'dfuDNBUSY',
5: 'dfuDNLOAD_IDLE',
6: 'dfuMANIFEST_SYNC',
7: 'dfuMANIFEST',
8: 'dfuMANIFEST_WAIT_RESET',
9: 'dfuUPLOAD_IDLE',
10: 'dfuERROR',
}
State.create_from_map()
class Status(Enumeration):
map = {
0x00: 'OK',
0x01: 'errTARGET',
0x02: 'errFILE',
0x03: 'errWRITE',
0x04: 'errERASE',
0x05: 'errCHECK_ERASED',
0x06: 'errPROG',
0x07: 'errVERIFY',
0x08: 'errADDRESS',
0x09: 'errNOTDONE',
0x0A: 'errFIRMWARE',
0x0B: 'errVENDOR',
0x0C: 'errUSBR',
0x0D: 'errPOR',
0x0E: 'errUNKNOWN',
0x0F: 'errSTALLEDPKT',
}
Status.create_from_map()
class DFU(object):
def __init__(self, device):
self._device = device
def detach(self):
self._device.ctrl_transfer(0x21, Request.DETACH, 0, 0, None)
def download(self, block_number, data):
"""Download a block to RAM or Flash."""
self._device.ctrl_transfer(0x21, Request.DNLOAD, block_number, 0, data)
def masserase(self):
"""Mass erase all of Flash memory. OTP left intact."""
self._device.ctrl_transfer(bmRequestType=0x21,
bRequest=Request.DNLOAD,
wValue=0,
wIndex=0,
data_or_wLength=[0x41])
# Erasure doesn't occur until we check the status.
status = self.get_status()
print("The device is now erasing itself.")
print("This will result in a USB disconnect.")
return True
def go(self, address=0x08000000):
"""Branch to the address at addrptr+4."""
# Branches to the value stored at the word *AFTER* this address.
self.setaddresspointer(address)
# Use DNLOAD, not DETACH.
self._device.ctrl_transfer(bmRequestType=0x21,
bRequest=Request.DNLOAD,
# bRequest=Request.DETACH,
wValue=0,
wIndex=0,
data_or_wLength=[]) # Zero length.
# Execution.
print(self.get_status())
print("The device is now executing its application.")
print("This will result in a USB disconnect.")
return True
def readprotect(self):
"""Over the control word to protect the chip."""
# Option byte address.
adr = 0x1fffc000
self.setaddresspointer(adr)
# #Grab the old block.
# data=self.upload(2,2);
# #Second read verifies the state.
# status, timeout, state, discarded = dfu.get_status()
# print state;
# assert state==State.dfuUPLOAD_IDLE
# print "Grabbing old option bytes.";
# for foo in data:
# print "%02x" % foo;
# Write the new block.
# self._device.ctrl_transfer(0x21, Request.DNLOAD, 2, 0, [0xFF, 0xAA, 0x00, 0x55])
self.download(2, [0xFF, 0xFF])
# status check causes the write
status, timeout, state, discarded = dfu.get_status()
assert state == State.dfuDNBUSY;
def readunprotect(self):
"""Mass erase all of Flash memory. OTP left intact."""
self._device.ctrl_transfer(bmRequestType=0x21,
bRequest=Request.DNLOAD,
wValue=0,
wIndex=0,
data_or_wLength=[0x92])
print("Unprotecting the device.")
print("This will cause a USB disconnection.")
status = self.get_status()
return True
def upload(self, block_number, length):
# 2 to 2048 byte size for Flash, RAM, and System memory.
# option bytes should be equal to option byte block size.
# Other locations defined in Important Considerations in AN2606
# Address_Pointer is expected to have been already set by
# Address = ((wBlockNum - 2) * wTransferSize) + Address_Pointer,
# print "Requesting block 0x%08x size %06x" % (block_number,length);
if block_number > 0xFFFF:
print("WARNING: Block 0x%04x will be returned instead. (16-bit addr.)" % (block_number & 0xFFFF))
# data = self._device.ctrl_transfer(0xA1, Request.UPLOAD, block_number, 0, length)
data = self._device.ctrl_transfer(bmRequestType=0xA1,
bRequest=Request.UPLOAD,
wValue=block_number,
wIndex=0,
data_or_wLength=length)
return data
def setaddresspointer(self, address=application_offset):
"""Sets the address pointer in the STM32."""
byte0 = address & 0xFF
byte1 = (address >> 8) & 0xFF
byte2 = (address >> 16) & 0xFF
byte3 = (address >> 24) & 0xFF
data = [0x21, # Set pointer op-code
byte0, byte1, byte2, byte3 # Address, little-endian.
]
# print self.get_status();
toret = self._device.ctrl_transfer(bmRequestType=0x21,
bRequest=Request.DNLOAD,
wValue=0,
wIndex=0,
data_or_wLength=data)
# Address isn't set until first GETSTATUS query.
status = self.get_status()
# Second query is needed to check if correctly set.
# Failures result in dfuERROR or errTARGET.
status = self.get_status()
if status[2] == State.dfuDNLOAD_IDLE:
print("Setting address pointer to 0x%08x." % address)
# This will get us back to the entry point.
self.enter_dfu_mode()
else:
print("Failed to set address pointer.")
return False
return True
def get_status(self):
status_packed = self._device.ctrl_transfer(0xA1, Request.GETSTATUS, 0, 0, 6)
status = struct.unpack('<BBBBBB', status_packed)
return (Status.map[status[0]], (((status[1] << 8) | status[2]) << 8) | status[3],
State.map[status[4]], status[5])
def clear_status(self):
self._device.ctrl_transfer(0x21, Request.CLRSTATUS, 0, 0, None)
def get_state(self):
state_packed = self._device.ctrl_transfer(0xA1, Request.GETSTATE, 0, 0, 1)
return State.map[struct.unpack('<B', state_packed)[0]]
def abort(self):
self._device.ctrl_transfer(0x21, Request.ABORT, 0, 0, None)
def enter_dfu_mode(self):
action_map = {
State.dfuDNLOAD_SYNC: self.abort,
State.dfuDNLOAD_IDLE: self.abort,
State.dfuMANIFEST_SYNC: self.abort,
State.dfuUPLOAD_IDLE: self.abort,
State.dfuERROR: self.clear_status,
State.appIDLE: self.detach,
State.appDETACH: self._wait,
State.dfuDNBUSY: self._wait,
State.dfuMANIFEST: self.abort,
State.dfuMANIFEST_WAIT_RESET: self._wait,
State.dfuIDLE: self._wait
}
while True:
state = self.get_state()
if state == State.dfuIDLE:
break
action = action_map[state]
action()
def _wait(self):
time.sleep(0.1)
def download(dfu, data, flash_address):
# block_size = 1 << 8
sector_size = 1 << 12
print("Flashing to 0x%08x" % flash_address)
base_address = flash_address
# Rebase the address pointer.
if not dfu.setaddresspointer(base_address):
print("Failed to set address.")
sys.exit(1)
flash_address = flash_address + block_size * 2 - base_address # Correct offset.
if flash_address & (sector_size - 1) != 0:
raise Exception('Download must start at flash sector boundary')
block_number = flash_address / block_size
assert block_number * block_size == flash_address
print("Based from 0x%08x" % base_address)
try:
while len(data) > 0:
packet, data = data[:block_size], data[block_size:]
if len(packet) < block_size:
print("Padding a short packet.")
packet += '\xFF' * (block_size - len(packet))
# print "Downloading block %i." % block_number;
dfu.download(block_number, packet)
# status check causes the write
status, timeout, state, discarded = dfu.get_status()
assert state == State.dfuDNBUSY;
# Second read verifies the state.
status, timeout, state, discarded = dfu.get_status()
assert state == State.dfuDNLOAD_IDLE
sys.stdout.write('.')
sys.stdout.flush()
block_number += 1
finally:
print()
dfu.enter_dfu_mode()
def upload(dfu, flash_address, length, path):
"""Uploads a region from the chip to a file on the workstation."""
# Set the base address, then make it zero.
base_address = flash_address
# flash_address=base_address;
# Rebase the address pointer.
if not dfu.setaddresspointer(base_address):
print("Failed to set address.")
sys.exit(1)
flash_address = flash_address + block_size * 2 - base_address # Correct offset.
if flash_address & (block_size - 1) != 0:
raise Exception('Upload must start at block boundary')
block_number = flash_address / block_size
# assert block_number * block_size == flash_address #Ubertooth, not STM32
# address_pointer=0;
# assert flash_address==((block_number-2)*block_size)+address_pointer;
print("flash_address = %08x" % flash_address)
print("block_number = %08x" % block_number)
print("block_size = %08x" % block_size)
f = open(path, 'wb')
try:
while length > 0:
data = dfu.upload(block_number, block_size)
status, timeout, state, discarded = dfu.get_status()
sys.stdout.write('.')
sys.stdout.flush()
if len(data) == block_size:
f.write(data)
block_number += 1
length -= len(data)
else:
# raise Exception('Upload failed to read full block')
print("Failed to return full block number 0x%x" % block_number)
print("Got 0x%i bytes." % len(data))
finally:
f.close()
print()
def detach(dfu):
if dfu.get_state() == State.dfuIDLE:
dfu.detach()
print('Detached')
else:
print('In unexpected state: %s' % dfu.get_state())
def init_dfu(idVendor=stm32_vendor, idProduct=stm32_product):
dev = usb.core.find(idVendor=idVendor, idProduct=idProduct)
if dev is None:
raise RuntimeError('Device not found')
dfu = DFU(dev)
dev.default_timeout = 3000
try:
dfu.enter_dfu_mode()
except usb.core.USBError as e:
if len(e.args) > 0 and e.args[0] == 'Pipe error':
raise RuntimeError('Failed to enter DFU mode. Is bootloader running?')
else:
raise e
return dfu
def usage():
print("""
Usage: stm32-dfu <command> <arguments>
Write a file to application flash region:
stm32-dfu writeflash $file
Write a file to RAM at 0x20002000, after DFU region.
stm32-dfu writeram $file
Write a file to an arbitrary address.
stm32-dfu write $file $adr
Read data from application flash region and write to a file:
stm32-dfu read <filename>
Read data from SRAM region and write to a file:
stm32-dfu readram <filename>
Read data from ROM region and write to a file:
stm32-dfu readrom <filename>
Read data from OTP region and write to a file:
stm32-dfu readotp <filename>
Mass erase STM32 in preparation for reflashing.
stm32-dfu erase
Unprotect the STM32's RDP.
stm32-dfu unprotect
Protect the STM32's RDP.
stm32-dfu protect
Detach the bootloader and execute the flash application.
stm32-dfu go [0x08000000]
Detach the bootloader and execute from RAM at 0x20002000.
stm32-dfu goram
""")
def main():
if len(sys.argv) > 1:
if sys.argv[1] == 'read':
dfu = init_dfu()
upload(dfu, application_offset, application_size, sys.argv[2])
print('Read complete')
elif sys.argv[1] == 'readram':
dfu = init_dfu()
upload(dfu, ram_offset, ram_size, sys.argv[2])
print('Read complete')
elif sys.argv[1] == 'readrom':
dfu = init_dfu()
upload(dfu, rom_offset, rom_size, sys.argv[2])
print('Read complete')
elif sys.argv[1] == 'readotp':
dfu = init_dfu()
upload(dfu, otp_offset, otp_size, sys.argv[2])
print('Read complete')
elif sys.argv[1] == 'write':
f = open(sys.argv[2], 'rb')
data = f.read()
f.close()
dfu = init_dfu()
firmware = data
application_offset = int(sys.argv[3], 16)
download(dfu, firmware, application_offset)
print('Write complete')
elif sys.argv[1] == 'writeflash':
f = open(sys.argv[2], 'rb')
data = f.read()
f.close()
dfu = init_dfu()
firmware = data
download(dfu, firmware, application_offset)
print('Write complete')
elif sys.argv[1] == 'writeram':
f = open(sys.argv[2], 'rb')
data = f.read()
f.close()
dfu = init_dfu()
firmware = data
download(dfu, firmware, 0x20002000)
print('Write complete')
elif sys.argv[1] == 'detach':
dfu = init_dfu()
detach(dfu)
elif sys.argv[1] == 'erase':
dfu = init_dfu()
dfu.masserase()
elif sys.argv[1] == 'go':
dfu = init_dfu()
if len(sys.argv) == 2:
dfu.go()
else:
dfu.go(int(sys.argv[2], 16))
elif sys.argv[1] == 'unprotect':
dfu = init_dfu()
dfu.readunprotect()
elif sys.argv[1] == 'protect':
dfu = init_dfu()
dfu.readprotect()
else:
usage()
else:
usage()
if __name__ == '__main__':
main()