aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorGareth McMullin2011-02-04 20:25:12 +1300
committerGareth McMullin2011-02-04 20:25:12 +1300
commit69d790fcf6f2a1c62ad1898031b4c8c0571bad05 (patch)
treec5802a5fc0374dfa0a2144d68cf7d46d26b19a1c /scripts
parent406617a2a470021d9412e9280feda0d28bdb653b (diff)
Added programming scripts.
Diffstat (limited to 'scripts')
-rw-r--r--scripts/README9
-rwxr-xr-xscripts/bootprog.py188
-rw-r--r--scripts/dfu.py192
-rw-r--r--scripts/gdb.py301
-rwxr-xr-xscripts/hexprog.py175
-rwxr-xr-xscripts/stm32_mem.py109
-rw-r--r--scripts/stubs/Makefile12
-rw-r--r--scripts/stubs/stm32_opterase.S53
-rw-r--r--scripts/stubs/stm32_optprog.S52
9 files changed, 1091 insertions, 0 deletions
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 0000000..a539475
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,9 @@
+This directory contains some useful scripts for working
+on the Black Magic Debug project.
+
+bootprog.py - Production programmer using the STM32 SystemMemory bootloader.
+hexprog.py - Write an Intel hex file to a target using the GDB protocol.
+stm32_mem.py - Access STM32 Flash memory using USB DFU class interface.
+
+stubs/ - Source code for the microcode strings included in hexprog.py.
+
diff --git a/scripts/bootprog.py b/scripts/bootprog.py
new file mode 100755
index 0000000..ea11f8e
--- /dev/null
+++ b/scripts/bootprog.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python
+#
+# bootprog.py: STM32 SystemMemory Production Programmer -- version 1.1
+# Copyright (C) 2009 Black Sphere Technologies
+# Written by Gareth McMullin <gareth@blacksphere.co.nz>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import serial
+import struct
+from time import sleep
+
+class stm32_boot:
+ def __init__(self, port, baud=115200):
+ self.serial = serial.Serial(port, baud, 8, 'E', 1,
+ timeout=1)
+
+ # Turn on target device in SystemMemory boot mode
+ self.serial.setDTR(1)
+ sleep(0.1);
+
+ self._sync()
+
+ def _sync(self):
+ # Send sync byte
+ #print "sending sync byte"
+ self.serial.write("\x7F")
+ self._checkack()
+
+ def _sendcmd(self, cmd):
+ if type(cmd) == int:
+ cmd = chr(cmd)
+ cmd += chr(ord(cmd) ^ 0xff)
+ #print "sendcmd:", repr(cmd)
+ self.serial.write(cmd)
+
+ def _send(self, data):
+ csum = 0
+ for c in data: csum ^= ord(c)
+ data = data + chr(csum)
+ #print "sending:", repr(data)
+ self.serial.write(data)
+
+ def _checkack(self):
+ ACK = "\x79"
+ b = self.serial.read(1)
+ if b != ACK: raise Exception("Invalid ack: %r" % b)
+ #print "got ack!"
+
+
+
+ def get(self):
+ self._sendcmd("\x00")
+ self._checkack()
+ num = ord(self.serial.read(1))
+ data = self.serial.read(num+1)
+ self._checkack()
+ return data
+
+ def eraseall(self):
+ # Send erase cmd
+ self._sendcmd("\x43")
+ self._checkack()
+ # Global erase
+ self._sendcmd("\xff")
+ self._checkack()
+
+ def read(self, addr, len):
+ # Send read cmd
+ self._sendcmd("\x11")
+ self._checkack()
+ # Send address
+ self._send(struct.pack(">L", addr))
+ self._checkack()
+ # Send length
+ self._sendcmd(chr(len-1))
+ self._checkack()
+ return self.serial.read(len)
+
+ def write(self, addr, data):
+ # Send write cmd
+ self._sendcmd("\x31")
+ self._checkack()
+ # Send address
+ self._send(struct.pack(">L", addr))
+ self._checkack()
+ # Send data
+ self._send(chr(len(data)-1) + data)
+ self._checkack()
+
+
+ def write_protect(self, sectors):
+ # Send WP cmd
+ self._sendcmd("\x63")
+ self._checkack()
+ # Send sector list
+ self._send(chr(len(sectors)-1) + "".join(chr(i) for i in sectors))
+ self._checkack()
+ # Resync after system reset
+ self._sync()
+
+ def write_unprotect(self):
+ self._sendcmd("\x73")
+ self._checkack()
+ self._checkack()
+ self._sync()
+
+ def read_protect(self):
+ self._sendcmd("\x82")
+ self._checkack()
+ self._checkack()
+ self._sync()
+
+ def read_unprotect(self):
+ self._sendcmd("\x92")
+ self._checkack()
+ self._checkack()
+ self._sync()
+
+
+if __name__ == "__main__":
+ from sys import stdout, argv, platform
+ from getopt import getopt
+
+ if platform == "linux2":
+ print "\x1b\x5b\x48\x1b\x5b\x32\x4a" # clear terminal screen
+ print "STM32 SystemMemory Production Programmer -- version 1.1"
+ print "Copyright (C) 2009 Black Sphere Technologies, All rights reserved."
+ print
+
+ dev = "COM1" if platform == "win32" else "/dev/ttyUSB0"
+ baud = 115200
+ addr = 0x8000000
+ try:
+ opts, args = getopt(argv[1:], "b:d:a:")
+ for opt in opts:
+ if opt[0] == "-b": baud = int(opt[1])
+ elif opt[0] == "-d": dev = opt[1]
+ else: raise Exception()
+
+ progfile = args[0]
+ except:
+ print "Usage %s [-d <dev>] [-b <baudrate>] [-a <address>] <filename>" % argv[0]
+ print "\t-d : Use target on interface <dev> (default: %s)" % dev
+ print "\t-b : Set device baudrate (default: %d)" % baud
+ print "\t-a : Set programming address (default: 0x%X)" % addr
+ print
+ exit(-1)
+
+ prog = open(progfile, "rb").read()
+ boot = stm32_boot(dev, baud)
+
+ cmds = boot.get()
+ print "Target bootloader version: %d.%d\n" % (ord(cmds[0]) >> 4, ord(cmds[0]) % 0xf)
+
+ print "Removing device protection..."
+ boot.read_unprotect()
+ boot.write_unprotect()
+ print "Erasing target device..."
+ boot.eraseall()
+ addr = 0x8000000
+ while prog:
+ print ("Programming address 0x%08X..0x%08X...\r" % (addr, addr + min(len(prog), 255))),
+ stdout.flush();
+ boot.write(addr, prog[:256])
+ addr += 256
+ prog = prog[256:]
+
+ print
+ print "Enabling device protection..."
+ boot.write_protect(range(0,2))
+ #boot.read_protect()
+
+ print "All operations completed."
+ print
+
+
diff --git a/scripts/dfu.py b/scripts/dfu.py
new file mode 100644
index 0000000..2e4ad66
--- /dev/null
+++ b/scripts/dfu.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+#
+# dfu.py: Access USB DFU class devices
+# Copyright (C) 2009 Black Sphere Technologies
+# Written by Gareth McMullin <gareth@blacksphere.co.nz>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import usb
+
+DFU_DETACH_TIMEOUT = 1000
+
+# DFU Requests
+DFU_DETACH = 0x00
+DFU_DNLOAD = 0x01
+DFU_UPLOAD = 0x02
+DFU_GETSTATUS = 0x03
+DFU_CLRSTATUS = 0x04
+DFU_GETSTATE = 0x05
+DFU_ABORT = 0x06
+
+# DFU States
+STATE_APP_IDLE = 0x00
+STATE_APP_DETACH = 0x01
+STATE_DFU_IDLE = 0x02
+STATE_DFU_DOWNLOAD_SYNC = 0x03
+STATE_DFU_DOWNLOAD_BUSY = 0x04
+STATE_DFU_DOWNLOAD_IDLE = 0x05
+STATE_DFU_MANIFEST_SYNC = 0x06
+STATE_DFU_MANIFEST = 0x07
+STATE_DFU_MANIFEST_WAIT_RESET = 0x08
+STATE_DFU_UPLOAD_IDLE = 0x09
+STATE_DFU_ERROR = 0x0a
+DFU_STATUS_OK = 0x00
+
+# DFU Status cides
+DFU_STATUS_ERROR_TARGET = 0x01
+DFU_STATUS_ERROR_FILE = 0x02
+DFU_STATUS_ERROR_WRITE = 0x03
+DFU_STATUS_ERROR_ERASE = 0x04
+DFU_STATUS_ERROR_CHECK_ERASED = 0x05
+DFU_STATUS_ERROR_PROG = 0x06
+DFU_STATUS_ERROR_VERIFY = 0x07
+DFU_STATUS_ERROR_ADDRESS = 0x08
+DFU_STATUS_ERROR_NOTDONE = 0x09
+DFU_STATUS_ERROR_FIRMWARE = 0x0a
+DFU_STATUS_ERROR_VENDOR = 0x0b
+DFU_STATUS_ERROR_USBR = 0x0c
+DFU_STATUS_ERROR_POR = 0x0d
+DFU_STATUS_ERROR_UNKNOWN = 0x0e
+DFU_STATUS_ERROR_STALLEDPKT = 0x0f
+
+class dfu_status(object):
+ def __init__(self, buf):
+ self.bStatus = buf[0]
+ self.bwPollTimeout = buf[1] + (buf[2]<<8) + (buf[3]<<16)
+ self.bState = buf[4]
+ self.iString = buf[5]
+
+
+class dfu_device(object):
+ def __init__(self, dev, conf, iface):
+ self.dev = dev
+ self.conf = conf
+ self.iface = iface
+ self.handle = self.dev.open()
+ try:
+ self.handle.setConfiguration(conf)
+ except: pass
+ self.handle.claimInterface(iface)
+ self.handle.setAltInterface(iface)
+ if type(self.iface) is usb.Interface:
+ self.index = self.iface.interfaceNumber
+ else: self.index = self.iface
+
+ def detach(self, wTimeout=255):
+ self.handle.controlMsg(usb.ENDPOINT_OUT | usb.TYPE_CLASS |
+ usb.RECIP_INTERFACE, DFU_DETACH,
+ None, value=wTimeout, index=self.index)
+
+ def download(self, wBlockNum, data):
+ self.handle.controlMsg(usb.ENDPOINT_OUT | usb.TYPE_CLASS |
+ usb.RECIP_INTERFACE, DFU_DNLOAD,
+ data, value=wBlockNum, index=self.index)
+
+ def upload(self, wBlockNum, length):
+ return self.handle.controlMsg(usb.ENDPOINT_IN |
+ usb.TYPE_CLASS | usb.RECIP_INTERFACE, DFU_UPLOAD,
+ length, value=wBlockNum, index=self.index)
+
+ def get_status(self):
+ buf = self.handle.controlMsg(usb.ENDPOINT_IN |
+ usb.TYPE_CLASS | usb.RECIP_INTERFACE, DFU_GETSTATUS,
+ 6, index=self.index)
+ return dfu_status(buf)
+
+ def clear_status(self):
+ self.handle.controlMsg(usb.ENDPOINT_OUT | usb.TYPE_CLASS |
+ usb.RECIP_INTERFACE, DFU_CLRSTATUS,
+ "", index=0)
+
+ def get_state(self):
+ buf = self.handle.controlMsg(usb.ENDPOINT_IN |
+ usb.TYPE_CLASS | usb.RECIP_INTERFACE, DFU_GETSTATE,
+ 1, index=self.index)
+ return buf[0]
+
+ def abort(self):
+ self.handle.controlMsg(usb.ENDPOINT_OUT | usb.TYPE_CLASS |
+ usb.RECIP_INTERFACE, DFU_ABORT,
+ None, index=self.index)
+
+
+ def make_idle(self):
+ retries = 3
+ while retries:
+ try:
+ status = self.get_status()
+ except:
+ self.clear_status()
+ continue
+
+ retries -= 1
+
+ if status.bState == STATE_DFU_IDLE:
+ return True
+
+ if ((status.bState == STATE_DFU_DOWNLOAD_SYNC) or
+ (status.bState == STATE_DFU_DOWNLOAD_IDLE) or
+ (status.bState == STATE_DFU_MANIFEST_SYNC) or
+ (status.bState == STATE_DFU_UPLOAD_IDLE) or
+ (status.bState == STATE_DFU_DOWNLOAD_BUSY) or
+ (status.bState == STATE_DFU_MANIFEST)):
+ self.abort()
+ continue
+
+ if status.bState == STATE_DFU_ERROR:
+ self.clear_status()
+ continue
+
+ if status.bState == STATE_APP_IDLE:
+ self.detach(DFU_DETACH_TIMEOUT)
+ continue
+
+ if ((status.bState == STATE_APP_DETACH) or
+ (status.bState == STATE_DFU_MANIFEST_WAIT_RESET)):
+ usb.reset(self.handle)
+ return False
+
+ raise Exception
+
+def finddevs():
+ devs = []
+ for bus in usb.busses():
+ for dev in bus.devices:
+ for conf in dev.configurations:
+ for ifaces in conf.interfaces:
+ for iface in ifaces:
+ if ((iface.interfaceClass == 0xFE) and
+ (iface.interfaceSubClass == 0x01)):
+ devs.append((dev, conf, iface))
+ return devs
+
+
+if __name__ == "__main__":
+ devs = finddevs()
+ if not devs:
+ print "No devices found!"
+ exit(-1)
+
+ for dfu in devs:
+ handle = dfu[0].open()
+ man = handle.getString(dfu[0].iManufacturer, 30)
+ product = handle.getString(dfu[0].iProduct, 30)
+ iname = handle.getString(dfu[2].iInterface, 30)
+ print "Device %s: ID %04x:%04x %s - %s" % (dfu[0].filename,
+ dfu[0].idVendor, dfu[0].idProduct, man, product)
+
+
+
diff --git a/scripts/gdb.py b/scripts/gdb.py
new file mode 100644
index 0000000..fc3572c
--- /dev/null
+++ b/scripts/gdb.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+#
+# gdb.py: Python module for low level GDB protocol implementation
+# Copyright (C) 2009 Black Sphere Technologies
+# Written by Gareth McMullin <gareth@blacksphere.co.nz>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# Used to parse XML memory map from target
+from xml.dom.minidom import parseString
+import struct
+import time
+
+def hexify(s):
+ """Convert a binary string into hex representation"""
+ ret = ''
+ for c in s:
+ ret += "%02X" % ord(c)
+ return ret
+
+def unhexify(s):
+ """Convert a hex string into binary representation"""
+ ret = ''
+ for i in range(0, len(s), 2):
+ ret += chr(int(s[i:i+2], 16))
+ return ret
+
+class FakeSocket:
+ """Emulate socket functions send and recv on a file object"""
+ def __init__(self, file):
+ self.file = file
+
+ def send(self, data):
+ self.file.write(data)
+
+ def recv(self, bufsize):
+ return self.file.read(bufsize)
+
+class Target:
+ def __init__(self, sock):
+ if "send" in dir(sock):
+ self.sock = sock
+ else:
+ self.sock = FakeSocket(sock)
+
+ def getpacket(self):
+ """Return the first correctly received packet from GDB target"""
+ while True:
+ while self.sock.recv(1) != '$': pass
+ csum = 0
+ packet = ''
+
+ while True:
+ c = self.sock.recv(1)
+ if c == '#': break
+
+ if c == '$':
+ packet = ''
+ csum = 0
+ continue
+
+ if c == '}':
+ c = self.sock.recv(1)
+ csum += ord(c) + ord('}')
+ packet += chr(ord(c) ^ 0x20)
+ continue
+
+ packet += c
+ csum += ord(c)
+
+ if (csum & 0xFF) == int(self.sock.recv(2),16): break
+
+ self.sock.send('-')
+
+ self.sock.send('+')
+ return packet
+
+
+ def putpacket(self, packet):
+ """Send packet to GDB target and wait for acknowledge"""
+ while True:
+ self.sock.send('$')
+ csum = 0
+ for c in packet:
+ if (c == '$') or (c == '#') or (c == '}'):
+ self.sock.send('}')
+ self.sock.send(chr(ord(c) ^ 0x20))
+ csum += (ord(c) ^ 0x20) + ord('}')
+ else:
+ self.sock.send(c)
+ csum += ord(c)
+ self.sock.send('#')
+ self.sock.send("%02X" % (csum & 0xFF))
+ if self.sock.recv(1) == '+': break
+
+ def monitor(self, cmd):
+ """Send gdb "monitor" command to target"""
+ ret = []
+ self.putpacket("qRcmd," + hexify(cmd))
+
+ while True:
+ s = self.getpacket()
+ if s == '': return None
+ if s == 'OK': return ret
+ if s.startswith('O'): ret.append(unhexify(s[1:]))
+ else:
+ raise Exception('Invalid GDB stub response')
+
+ def attach(self, pid):
+ """Attach to target process (gdb "attach" command)"""
+ self.putpacket("vAttach;%08X" % pid)
+ reply = self.getpacket()
+ if (len(reply) == 0) or (reply[0] == 'E'):
+ raise Exception('Failed to attach to remote pid %d' % pid)
+
+ def detach(self):
+ """Detach from target process (gdb "detach" command)"""
+ self.putpacket("D")
+ if self.getpacket() != 'OK':
+ raise Exception("Failed to detach from remote process")
+
+ def reset(self):
+ """Reset the target system"""
+ self.putpacket("r")
+
+ def read_mem(self, addr, length):
+ """Read length bytes from target at address addr"""
+ self.putpacket("m%08X,%08X" % (addr, length))
+ reply = self.getpacket()
+ if (len(reply) == 0) or (reply[0] == 'E'):
+ raise Exception('Error reading memory at 0x%08X' % addr)
+ try:
+ data = unhexify(reply)
+ except Excpetion:
+ raise Exception('Invalid response to memory read packet: %r' % reply)
+ return data
+
+ def write_mem(self, addr, data):
+ """Write data to target at address addr"""
+ self.putpacket("X%08X,%08X:%s" % (addr, len(data), data))
+ if self.getpacket() != 'OK':
+ raise Exception('Error writing to memory at 0x%08X' % addr)
+
+ def read_regs(self):
+ """Read target core registers"""
+ self.putpacket("g")
+ reply = self.getpacket()
+ if (len(reply) == 0) or (reply[0] == 'E'):
+ raise Exception('Error reading memory at 0x%08X' % addr)
+ try:
+ data = unhexify(reply)
+ except Excpetion:
+ raise Exception('Invalid response to memory read packet: %r' % reply)
+ return struct.unpack("16L", data)
+
+ def write_regs(self, *regs):
+ """Write target core registers"""
+ data = struct.pack("%dL" % len(regs), *regs)
+ self.putpacket("G" + hexify(data))
+ if self.getpacket() != 'OK':
+ raise Exception('Error writing to target core registers')
+
+ def memmap_read(self):
+ """Read the XML memory map from target"""
+ offset = 0
+ ret = ''
+ while True:
+ self.putpacket("qXfer:memory-map:read::%08X,%08X" % (offset, 512))
+ reply = self.getpacket()
+ if (reply[0] == 'm') or (reply[0] == 'l'):
+ offset += len(reply) - 1
+ ret += reply[1:]
+ else:
+ raise Exception("Invalid GDB stub response")
+
+ if reply[0] == 'l': return ret
+
+ def resume(self):
+ """Resume target execution"""
+ self.putpacket("c")
+
+ def interrupt(self):
+ """Interrupt target execution"""
+ self.sock.send("\x03")
+
+ def run_stub(self, stub, address, *args):
+ """Execute a binary stub at address, passing args in core registers."""
+ self.reset() # Ensure processor is in sane state
+ time.sleep(0.1)
+ self.write_mem(address, stub)
+ regs = list(self.read_regs())
+ regs[:len(args)] = args
+ regs[15] = address
+ self.write_regs(*regs)
+ self.resume()
+ reply = self.getpacket()
+ while not reply:
+ reply = self.getpacket()
+ if not reply.startswith("T05"):
+ raise Exception("Invalid stop response: %r" % reply)
+
+ class FlashMemory:
+ def __init__(self, target, offset, length, blocksize):
+ self.target = target
+ self.offset = offset
+ self.length = length
+ self.blocksize = blocksize
+ self.blocks = list(None for i in range(length / blocksize))
+
+ def prog(self, offset, data):
+ assert ((offset >= self.offset) and
+ (offset + len(data) <= self.offset + self.length))
+
+ while data:
+ index = (offset - self.offset) / self.blocksize
+ bloffset = (offset - self.offset) % self.blocksize
+ bldata = data[:self.blocksize-bloffset]
+ data = data[len(bldata):]; offset += len(bldata)
+ if self.blocks[index] is None: # Initialize a clear block
+ self.blocks[index] = "".join(chr(0xff) for i in range(self.blocksize))
+ self.blocks[index] = (self.blocks[index][:bloffset] + bldata +
+ self.blocks[index][bloffset+len(bldata):])
+
+ def commit(self, progress_cb=None):
+ totalblocks = 0
+ for b in self.blocks:
+ if b is not None: totalblocks += 1
+
+ block = 0
+ for i in range(len(self.blocks)):
+ block += 1
+ if callable(progress_cb):
+ progress_cb(block*100/totalblocks)
+
+ # Erase the block
+ data = self.blocks[i]
+ addr = self.offset + self.blocksize * i
+ if data is None: continue
+ #print "Erasing flash at 0x%X" % (self.offset + self.blocksize*i)
+ self.target.putpacket("vFlashErase:%08X,%08X" %
+ (self.offset + self.blocksize*i, self.blocksize))
+ if self.target.getpacket() != 'OK':
+ raise Exception("Failed to erase flash")
+
+ while data:
+ d = data[0:980]
+ data = data[len(d):]
+ #print "Writing %d bytes at 0x%X" % (len(d), addr)
+ self.target.putpacket("vFlashWrite:%08X:%s" % (addr, d))
+ addr += len(d)
+ if self.target.getpacket() != 'OK':
+ raise Exception("Failed to write flash")
+
+ self.target.putpacket("vFlashDone")
+ if self.target.getpacket() != 'OK':
+ raise Exception("Failed to commit")
+
+ self.blocks = list(None for i in range(self.length / self.blocksize))
+
+
+ def flash_probe(self):
+ self.mem = []
+ xmldom = parseString(self.memmap_read())
+
+ for memrange in xmldom.getElementsByTagName("memory"):
+ if memrange.getAttribute("type") != "flash": continue
+ offset = eval(memrange.getAttribute("start"))
+ length = eval(memrange.getAttribute("length"))
+ for property in memrange.getElementsByTagName("property"):
+ if property.getAttribute("name") == "blocksize":
+ blocksize = eval(property.firstChild.data)
+ break
+ mem = Target.FlashMemory(self, offset, length, blocksize)
+ self.mem.append(mem)
+
+ xmldom.unlink()
+
+ return self.mem
+
+ def flash_write_prepare(self, address, data):
+ for m in self.mem:
+ if (address >= m.offset) and (address + len(data) < m.offset + m.length):
+ m.prog(address, data)
+
+ def flash_commit(self, progress_cb=None):
+ for m in self.mem:
+ m.commit(progress_cb)
+
+
diff --git a/scripts/hexprog.py b/scripts/hexprog.py
new file mode 100755
index 0000000..811c6a2
--- /dev/null
+++ b/scripts/hexprog.py
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+
+# hexprog.py: Python application to flash a target with an Intel hex file
+# Copyright (C) 2009 Black Sphere Technologies
+# Written by Gareth McMullin <gareth@blacksphere.co.nz>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import struct
+import time
+
+# Microcode sequence to erase option bytes
+stub_opterase = '\nH\x0bIA`\x0bIA`\tI\x81`\tI\x81`\x01iA\xf0 \x01\x01aA\xf0@\x01\x01a\xc4hO\xf0\x01\x064B\xfa\xd1\x00\xbe\x00 \x02@#\x01gE\xab\x89\xef\xcd'
+# Microcode sequence to program option bytes
+stub_optprog = '\tJ\nKS`\nKS`\x08K\x93`\x08K\x93`\x13iC\xf0\x10\x03\x13a\x01\x80\xd4hO\xf0\x01\x064B\xfa\xd1\x00\xbe\x00 \x02@#\x01gE\xab\x89\xef\xcd'
+
+def flash_write_hex(target, hexfile, progress_cb=None):
+ target.flash_probe()
+ f = open(hexfile)
+ addrhi = 0
+ for line in f:
+ if line[0] != ':': raise Exception("Error in hex file")
+ reclen = int(line[1:3], 16)
+ addrlo = int(line[3:7], 16)
+ rectype = int(line[7:9], 16);
+ if sum(ord(x) for x in gdb.unhexify(line[1:11+reclen*2])) & 0xff != 0:
+ raise Exception("Checksum error in hex file")
+ if rectype == 0: # Data record
+ addr = (addrhi << 16) + addrlo
+ data = gdb.unhexify(line[9:9+reclen*2])
+ target.flash_write_prepare(addr, data)
+ pass
+ elif rectype == 4: # High address record
+ addrhi = int(line[9:13], 16)
+ pass
+ elif rectype == 5: # Entry record
+ pass
+ elif rectype == 1: # End of file record
+ break
+ else:
+ raise Exception("Invalid record in hex file")
+
+ try:
+ target.flash_commit(progress_cb)
+ except:
+ print "Flash write failed! Is device protected?\n"
+ exit(-1)
+
+
+if __name__ == "__main__":
+ from serial import Serial, SerialException
+ from sys import argv, platform, stdout
+ from getopt import getopt
+
+ if platform == "linux2":
+ print ("\x1b\x5b\x48\x1b\x5b\x32\x4a") # clear terminal screen
+ print("Black Magic Probe -- Target Production Programming Tool -- version 1.0")
+ print("Copyright (C) 2009 Black Sphere Technologies, All rights reserved.")
+ print("")
+
+ dev = "COM1" if platform == "win32" else "/dev/ttyACM0"
+ baud = 115200
+ scan = "jtag_scan"
+ targetno = 0
+ unprot = False; prot = False
+
+ try:
+ opts, args = getopt(argv[1:], "sd:b:t:rR")
+ for opt in opts:
+ if opt[0] == "-s": scan = "swdp_scan"
+ elif opt[0] == "-b": baud = int(opt[1])
+ elif opt[0] == "-d": dev = opt[1]
+ elif opt[0] == "-t": targetno = int(opt[1])
+ elif opt[0] == "-r": unprot = True
+ elif opt[0] == "-R": prot = True
+ else: raise Exception()
+
+ hexfile = args[0]
+ except:
+ print("Usage %s [-s] [-d <dev>] [-b <baudrate>] [-t <n>] <filename>" % argv[0])
+ print("\t-s : Use SW-DP instead of JTAG-DP")
+ print("\t-d : Use target on interface <dev> (default: %s)" % dev)
+ print("\t-b : Set device baudrate (default: %d)" % baud)
+ print("\t-t : Connect to target #n (default: %d)" % targetno)
+ print("\t-r : Clear flash read protection before programming")
+ print("\t-R : Enable flash read protection after programming (requires power-on reset)")
+ print("")
+ exit(-1)
+
+ try:
+ target = gdb.Target(Serial(dev, baud, timeout=3))
+ except SerialException, e:
+ print("FATAL: Failed to open serial device!\n%s\n" % e[0])
+ exit(-1)
+
+ try:
+ r = target.monitor("version")
+ for s in r: print s,
+ except SerialException, e:
+ print("FATAL: Serial communication failure!\n%s\n" % e[0])
+ exit(-1)
+ except: pass
+
+ print "Target device scan:"
+ targetlist = None
+ r = target.monitor(scan)
+ for s in r:
+ print s,
+ print
+
+ r = target.monitor("targets")
+ for s in r:
+ if s.startswith("No. Att Driver"): targetlist = []
+ try:
+ if type(targetlist) is list:
+ targetlist.append(int(s[:2]))
+ except: pass
+
+ #if not targetlist:
+ # print("FATAL: No usable targets found!\n")
+ # exit(-1)
+
+ if targetlist and (targetno not in targetlist):
+ print("WARNING: Selected target %d not available, using %d" % (targetno, targetlist[0]))
+ targetno = targetlist[0]
+
+ print("Attaching to target %d." % targetno)
+ target.attach(targetno)
+ time.sleep(0.1)
+
+ if unprot:
+ print("Removing device protection.")
+ # Save option bytes for later
+ #optbytes = struct.unpack("8H", target.read_mem(0x1FFFF800, 16))
+ # Remove protection
+ target.run_stub(stub_opterase, 0x20000000)
+ target.run_stub(stub_optprog, 0x20000000, 0x1FFFF800, 0x5aa5)
+ target.reset()
+ time.sleep(0.1)
+
+ for m in target.flash_probe():
+ print("FLASH memory -- Offset: 0x%X BlockSize:0x%X\n" % (m.offset, m.blocksize))
+
+ def progress(percent):
+ print ("Progress: %d%%\r" % percent),
+ stdout.flush()
+
+ print("Programming target")
+ flash_write_hex(target, hexfile, progress)
+
+ print("Resetting target")
+ target.reset()
+
+ if prot:
+ print("Enabling device protection.")
+ target.run_stub(stub_opterase, 0x20000000)
+ target.run_stub(stub_optprog, 0x20000000, 0x1FFFF800, 0x00ff)
+ target.reset()
+
+ target.detach()
+
+ print("\nAll operations complete!\n")
+
diff --git a/scripts/stm32_mem.py b/scripts/stm32_mem.py
new file mode 100755
index 0000000..ff2986e
--- /dev/null
+++ b/scripts/stm32_mem.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+#
+# stm32_mem.py: STM32 memory access using USB DFU class
+# Copyright (C) 2009 Black Sphere Technologies
+# Written by Gareth McMullin <gareth@blacksphere.co.nz>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from time import sleep
+import struct
+from sys import stdout, argv
+
+import usb
+import dfu
+
+CMD_GETCOMMANDS = 0x00
+CMD_SETADDRESSPOINTER = 0x21
+CMD_ERASE = 0x41
+
+def stm32_erase(dev, addr):
+ erase_cmd = struct.pack("<BL", CMD_ERASE, addr)
+ dev.download(0, erase_cmd)
+ while True:
+ status = dev.get_status()
+ if status.bState == dfu.STATE_DFU_DOWNLOAD_BUSY:
+ sleep(status.bwPollTimeout / 1000.0)
+ if status.bState == dfu.STATE_DFU_DOWNLOAD_IDLE:
+ break
+
+def stm32_write(dev, data):
+ dev.download(2, data)
+ while True:
+ status = dev.get_status()
+ if status.bState == dfu.STATE_DFU_DOWNLOAD_BUSY:
+ sleep(status.bwPollTimeout / 1000.0)
+ if status.bState == dfu.STATE_DFU_DOWNLOAD_IDLE:
+ break
+
+def stm32_manifest(dev):
+ dev.download(0, "")
+ while True:
+ try:
+ status = dev.get_status()
+ except:
+ return
+ sleep(status.bwPollTimeout / 1000.0)
+ if status.bState == dfu.STATE_DFU_MANIFEST:
+ break
+
+if __name__ == "__main__":
+ print
+ print "USB Device Firmware Upgrade - Host Utility -- version 1.1"
+ print "Copyright (C) 2009 Black Sphere Technologies"
+ print "All rights reserved"
+ print
+
+ devs = dfu.finddevs()
+ if not devs:
+ print "No devices found!"
+ exit(-1)
+
+ for dev in devs:
+ dfudev = dfu.dfu_device(*dev)
+ man = dfudev.handle.getString(dfudev.dev.iManufacturer, 30)
+ product = dfudev.handle.getString(dfudev.dev.iProduct, 30)
+ if man == "Black Sphere Technologies": break
+ if man == "STMicroelectronics": break
+
+ print "Device %s: ID %04x:%04x %s - %s" % (dfudev.dev.filename,
+ dfudev.dev.idVendor, dfudev.dev.idProduct, man, product)
+
+ try:
+ state = dfudev.get_state()
+ except:
+ print "Failed to read device state! Assuming APP_IDLE"
+ state = dfu.STATE_APP_IDLE
+ if state == dfu.STATE_APP_IDLE:
+ dfudev.detach()
+ print "Run again to upgrade firmware."
+ exit(0)
+
+ dfudev.make_idle()
+
+ bin = open(argv[1], "rb").read()
+
+ addr = 0x8002000
+ while bin:
+ print ("Programming memory at 0x%08X\r" % addr),
+ stdout.flush()
+ stm32_erase(dfudev, addr)
+ stm32_write(dfudev, bin[:1024])
+
+ bin = bin[1024:]
+ addr += 1024
+
+ stm32_manifest(dfudev)
+
+ print "\nAll operations complete!\n"
diff --git a/scripts/stubs/Makefile b/scripts/stubs/Makefile
new file mode 100644
index 0000000..eb588d8
--- /dev/null
+++ b/scripts/stubs/Makefile
@@ -0,0 +1,12 @@
+CROSS_COMPILE = stm32-
+CC = $(CROSS_COMPILE)gcc
+OBJCOPY = $(CROSS_COMPILE)objcopy
+
+all: stm32_opterase.bin stm32_optprog.bin
+
+%.bin: %.S
+ $(CC) -nostdlib -Wl,-Ttext,0x20000000 $<
+ $(OBJCOPY) -O binary a.out $@
+
+clean:
+ -rm *.bin
diff --git a/scripts/stubs/stm32_opterase.S b/scripts/stubs/stm32_opterase.S
new file mode 100644
index 0000000..aea83e7
--- /dev/null
+++ b/scripts/stubs/stm32_opterase.S
@@ -0,0 +1,53 @@
+@; Assembler sequence to erase option bytes on STM32
+@; Takes no parameters, ends with BKPT instruction
+.global _start
+
+.equ FLASHBASE, 0x40022000
+
+.equ KEY1, 0x45670123
+.equ KEY2, 0xCDEF89AB
+
+.equ FLASH_KEY, 0x04
+.equ FLASH_OPTKEY, 0x08
+.equ FLASH_CR, 0x10
+.equ FLASH_SR, 0x0C
+
+.equ OPTER, 0x20
+.equ STRT, 0x40
+
+.equ BSY, 0x01
+
+.syntax unified
+
+_start:
+ @; Load FLASH controller base address
+ ldr r0, =FLASHBASE
+
+ @; Do unlocking sequence
+ ldr r1, =KEY1
+ str r1, [r0, #FLASH_KEY]
+ ldr r1, =KEY2
+ str r1, [r0, #FLASH_KEY]
+
+ @; Same for option bytes
+ ldr r1, =KEY1
+ str r1, [r0, #FLASH_OPTKEY]
+ ldr r1, =KEY2
+ str r1, [r0, #FLASH_OPTKEY]
+
+ @; Set OPTER bit in FLASH_CR
+ ldr r1, [r0, #FLASH_CR]
+ orr r1, r1, #OPTER
+ str r1, [r0, #FLASH_CR]
+ @; Set STRT bit in FLASH_CR
+ orr r1, r1, #STRT
+ str r1, [r0, #FLASH_CR]
+
+_wait: @; Wait for BSY bit to clear
+ ldr r4, [r0, #FLASH_SR]
+ mov r6, #BSY
+ tst r4, r6
+ bne _wait
+
+ bkpt
+
diff --git a/scripts/stubs/stm32_optprog.S b/scripts/stubs/stm32_optprog.S
new file mode 100644
index 0000000..cc97cd6
--- /dev/null
+++ b/scripts/stubs/stm32_optprog.S
@@ -0,0 +1,52 @@
+@; Assembler sequence to program option bytes on STM32
+@; Takes option address in r0 and value in r1.
+@; Ends with BKPT instruction
+.global _start
+
+.equ FLASHBASE, 0x40022000
+
+.equ KEY1, 0x45670123
+.equ KEY2, 0xCDEF89AB
+
+.equ FLASH_KEY, 0x04
+.equ FLASH_OPTKEY, 0x08
+.equ FLASH_CR, 0x10
+.equ FLASH_SR, 0x0C
+
+.equ OPTPG, 0x10
+
+.equ BSY, 0x01
+
+.syntax unified
+
+_start:
+ @; Load FLASH controller base address
+ ldr r2, =FLASHBASE
+
+ @; Do unlocking sequence
+ ldr r3, =KEY1
+ str r3, [r2, #FLASH_KEY]
+ ldr r3, =KEY2
+ str r3, [r2, #FLASH_KEY]
+
+ @; Same for option bytes
+ ldr r3, =KEY1
+ str r3, [r2, #FLASH_OPTKEY]
+ ldr r3, =KEY2
+ str r3, [r2, #FLASH_OPTKEY]
+
+ @; Set OPTPG bit in FLASH_CR
+ ldr r3, [r2, #FLASH_CR]
+ orr r3, r3, #OPTPG
+ str r3, [r2, #FLASH_CR]
+ @; Write data at address
+ strh r1, [r0]
+
+_wait: @; Wait for BSY bit to clear
+ ldr r4, [r2, #FLASH_SR]
+ mov r6, #BSY
+ tst r4, r6
+ bne _wait
+
+ bkpt
+