+#!/usr/bin/env python
+#
+# A simple python client to speak to the donbL AVR boot loader
+# donb (donb@capitolhillconsultants.com)
+# October 7th, 2011
+#
+
+from intelhex import IntelHex
+import serial
+import string
+import array
+import time
+import re
+import os
+import sys
+
+# defaults
+#
+TIMEOUT=5
+
+global s
+global psize
+global signature
+global calibration
+
+# send header
+#
+def sendheader(t, l):
+ global s
+ if type(t) is int:
+ s.write(chr(t))
+ else:
+ s.write(t)
+ s.write(chr((l>>8)&0xff))
+ s.write(chr(l&0xff))
+ return 1
+
+# error names
+#
+def errname(x):
+ if ord(x) == 1:
+ return "BL_ERR_UNIMP"
+ elif ord(x) == 2:
+ return "BL_ERR_INVALID"
+ elif ord(x) == 3:
+ return "BL_ERR_CHECKSUM"
+ return ord(x)
+
+# get header
+#
+def getheader():
+ global s
+
+ # status byte
+ e = s.read()
+
+ # len
+ x = s.read()
+ y = s.read()
+ x = ((ord(x) << 8) | ord(y))
+
+ if ord(e) != 0:
+ print "getheader: BL_ERR_OK not found: ", errname(e)
+
+ return x
+
+# get value
+#
+def getvalue(x):
+ z = array.array('B')
+ while(x):
+ x -= 1
+ z.append(ord(s.read()))
+
+ return z
+
+# address
+#
+def addr(x):
+ sendheader(5, 4)
+
+ # address is always in hex
+ if type(x) == str:
+ x = string.atol(x, 16)
+
+ s.write(chr((x >> 24)&0xff))
+ s.write(chr((x >> 16)&0xff))
+ s.write(chr((x >> 8)&0xff))
+ s.write(chr((x >> 0)&0xff))
+
+ #print "addr: sending %x" % x
+
+ x = getheader()
+ if x != 4:
+ print "error: address setting failed"
+ return 0
+
+ x = getvalue(x)
+ #print "address received: %.02x%.02x%.02x%.02x" % (x[0], x[1], x[2], x[3])
+ #print "address set"
+
+ return 1
+
+# peek a word at an address
+#
+def peek(a):
+ addr(a)
+
+ sendheader(6, 0)
+
+ x = getheader()
+ if x != 2:
+ print "peek failed: message count incorrect"
+ return 0
+
+ x = getvalue(x)
+ x = ((x[0] << 8)|x[1])
+ print "peek %s: %.04x" % (a, x)
+
+ return 1
+
+# get the page size
+#
+def pagesz():
+ global psize
+
+ sendheader(3, 0)
+
+ x = getheader()
+ if x != 2:
+ print "pagesz failed: message count incorrect"
+ return 0
+
+ x = getvalue(x)
+ x = ((x[0] << 8) | x[1])
+ print "pagesz: %x" % x
+ psize = x
+
+ return 1
+
+# get the signature bytes and calibration byte
+#
+def getsignature():
+ sendheader(1, 0)
+
+ x = getheader()
+ if x != 4:
+ print "error: signature header size invalid"
+ return 0
+
+ x = getvalue(x)
+ signature = x[0:3]
+ calibration = x[3]
+ print "signature: %.02x %.02x %.02x" % (x[0], x[1], x[2])
+ print "rc calibration: %.02x" % (x[3])
+
+ return 1
+
+# get the fuse bytes
+#
+def fuse():
+ sendheader(2, 0)
+
+ x = getheader()
+ if x != 4:
+ print "fuse failed: message count incorrect"
+ return 0
+
+ x = getvalue(x)
+ print "fuse: %.02x %.02x %.02x %.02x" % (x[0], x[1], x[2], x[3])
+
+ return 1
+
+# exit the BLS command loop
+#
+def doexit():
+ sendheader(7, 0)
+ x = getheader()
+ if x != 0:
+ print "error: exit attempt failed"
+ return 0
+
+ return 1
+
+# initialize the SIM
+#
+def blsinit(p, b):
+ global s
+
+ # make sure that RST is tied to RESET on the AVR so we can force BLS to execute by driving RST low
+ s = serial.Serial(p, b, timeout=TIMEOUT, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, rtscts=0)
+ s.setRTS(1)
+ s.setDTR(1)
+ time.sleep(0.01)
+ s.flushInput()
+ s.setRTS(0)
+ s.setDTR(0)
+ time.sleep(0.01)
+
+ # based on a (presumed) bug in the AVR architecture, the BL wont
+ # accept USART input unless it first outputs a byte. Not sure why
+ # this occurs, but here is the compensating code:
+ x = s.read()
+ if x == None:
+ print "error: no paramedics available"
+ return 0
+ if x != '+':
+ print "error: unexpected value: '+' != %.02x" % (ord(x))
+ return 0
+
+ print "boot loader found. entering command mode."
+ s.write('?')
+
+ return 1
+
+# upload to bootloader
+#
+def upload(p, d):
+ # send the address first
+ if addr(p) != 1:
+ print "error: couldnt set page address"
+ return 0
+
+ sendheader(4, len(d))
+
+ # ship the payload
+ v = 0
+ for i in d:
+ v += i
+ s.write(chr(i))
+
+ # twos complement the sum
+ v = (0x100 - v) & 0xff
+
+ x = getheader()
+ if x != 1:
+ print "error: flash failed"
+ return 0
+
+ x = getvalue(x)
+ if v != x[0]:
+ print "error: checksums dont match: got=%.02x, expected=%.02x" % (x[0], v)
+ return 0
+
+ return 1
+
+# flash an image to the chip
+#
+def flash(f):
+ global psize
+
+ # make sure we have the page size first
+ pagesz()
+
+ # ingest the ihex file
+ h = IntelHex(f)
+
+ sys.stdout.write("flashing pages: ")
+
+ # always start with page zero; the code will auto-jump to
+ # the new address if there is no page zero in the hexfile
+ p = 0
+ d = []
+ n = -1
+ for a in h.addresses():
+ # if the page has changed, upload
+ if (a & ~((psize)-1)) != p:
+ # upload only if we have data
+ if len(d) > 0:
+ if upload(p, d) != 1:
+ print "error: upload failed"
+ return 0
+ sys.stdout.write(".")
+
+ # set the new page
+ p = (a & ~((psize)-1))
+
+ # start a new list with this address
+ d = []
+
+ # reset the previous pointer
+ n = -1
+
+ # insert the data at the page address
+ t = a & ((psize)-1)
+
+ # if this address isn't an increment above last, fill the void
+ while n + 1 < t:
+ d.insert(n, 0xff)
+ n += 1
+
+ # ensure n reflects this byte
+ n = t
+
+ # insert the actual data byte
+ d.insert(t, ord(h.gets(a, 1)))
+
+ # if iterating through addresses() has completed but we still have data,
+ # upload it now
+ if len(d) > 0:
+ if upload(p, d) != 1:
+ print "error: upload failed"
+ return 0
+ sys.stdout.write(".")
+
+ print ""
+
+ return 1
+
+# flash an image from the web
+#
+def fromweb():
+ global signature
+
+ # get the device ID
+ if getsignature() != 1:
+ print "error: can't retrieve chip signature"
+ return 0
+
+ fn="/tmp/.goodfet.hex"
+ print "fromweb: retrieving image for chip %.02x.%.02x.%.02x" % (signature[0], signature[1], signature[2])
+ os.system("curl http://pa-ri.sc/goodfet/fw/%.02x%.02x%.02x.hex > %s" % (signature[0], signature[1], signature[2], fn))
+
+ return flash(fn)
+
+# main
+#
+b = 500000
+p = "/dev/ttyUSB0"
+if len(sys.argv) > 1:
+ p = sys.argv[1]
+if len(sys.argv) > 2:
+ b = sys.argv[2]
+
+if blsinit(p, b) != 1:
+ print "error: couldnt initialize bls"
+ exit(1)
+
+while 1:
+ sys.stdout.write("donbL> ")
+
+ c = sys.stdin.readline().rstrip()
+ c = re.split("[ \t]", c)
+
+ if c[0] == "exit":
+ print "donbL: exiting"
+ doexit()
+ s.close()
+ break
+
+ if c[0] == "peek":
+ print "donbL: peeking address"
+ peek(c[1])
+
+ if c[0] == "address":
+ print "donbL: setting flash address"
+ addr(c[1])
+
+ if c[0] == "pagesz":
+ print "donbL: retrieving page size"
+ pagesz()
+
+ if c[0] == "signature":
+ print "donbL: retrieving avr signature"
+ getsignature()
+
+ if c[0] == "fuse":
+ print "donbL: retrieving avr fuse and lock bytes"
+ fuse()
+
+ if c[0] == "flash":
+ print "donbL: flashing image"
+ flash(c[1])
+
+ if c[0] == "fromweb":
+ print "donbL: flashing new image from web"
+ fromweb()
+
+ if c[0] == "reset":
+ s.close()
+ blsinit(p, b)
+