initial commit of the donbL AVR boot loader
[goodfet] / client / goodfet.donbL
1 #!/usr/bin/env python
2 #
3 # A simple python client to speak to the donbL AVR boot loader
4 # donb (donb@capitolhillconsultants.com)
5 # October 7th, 2011
6 #
7
8 from intelhex import IntelHex
9 import serial
10 import string
11 import array
12 import time
13 import re
14 import os
15 import sys
16
17 # defaults
18 #
19 TIMEOUT=5
20
21 global s
22 global psize
23 global signature
24 global calibration
25
26 # send header
27 #
28 def sendheader(t, l):
29         global s
30         if type(t) is int:
31                 s.write(chr(t))
32         else:
33                 s.write(t)
34         s.write(chr((l>>8)&0xff))
35         s.write(chr(l&0xff))
36         return 1
37
38 # error names
39 #
40 def errname(x):
41         if ord(x) == 1:
42                 return "BL_ERR_UNIMP"
43         elif ord(x) == 2:
44                 return "BL_ERR_INVALID"
45         elif ord(x) == 3:
46                 return "BL_ERR_CHECKSUM"
47         return ord(x)
48
49 # get header
50 #
51 def getheader():
52         global s
53
54         # status byte
55         e = s.read()
56
57         # len
58         x = s.read()
59         y = s.read()
60         x = ((ord(x) << 8) | ord(y))
61
62         if ord(e) != 0:
63                 print "getheader: BL_ERR_OK not found: ", errname(e)
64
65         return x
66
67 # get value
68 #
69 def getvalue(x):
70         z = array.array('B')
71         while(x):
72                 x -= 1
73                 z.append(ord(s.read()))
74
75         return z
76
77 # address
78 #
79 def addr(x):
80         sendheader(5, 4)
81
82         # address is always in hex
83         if type(x) == str:
84                 x = string.atol(x, 16)
85
86         s.write(chr((x >> 24)&0xff))
87         s.write(chr((x >> 16)&0xff))
88         s.write(chr((x >>  8)&0xff))
89         s.write(chr((x >>  0)&0xff))
90
91         #print "addr: sending %x" % x
92
93         x = getheader()
94         if x != 4:
95                 print "error: address setting failed"
96                 return 0
97
98         x = getvalue(x)
99         #print "address received: %.02x%.02x%.02x%.02x" % (x[0], x[1], x[2], x[3])
100         #print "address set"
101
102         return 1
103
104 # peek a word at an address
105 #
106 def peek(a):
107         addr(a)
108
109         sendheader(6, 0)
110
111         x = getheader()
112         if x != 2:
113                 print "peek failed: message count incorrect"
114                 return 0
115
116         x = getvalue(x)
117         x = ((x[0] << 8)|x[1])
118         print "peek %s: %.04x" % (a, x)
119
120         return 1
121
122 # get the page size
123 #
124 def pagesz():
125         global psize
126
127         sendheader(3, 0)
128
129         x = getheader()
130         if x != 2:
131                 print "pagesz failed: message count incorrect"
132                 return 0
133
134         x = getvalue(x)
135         x = ((x[0] << 8) | x[1])
136         print "pagesz: %x" % x
137         psize = x
138
139         return 1
140
141 # get the signature bytes and calibration byte
142 #
143 def getsignature():
144         sendheader(1, 0)
145
146         x = getheader()
147         if x != 4:
148                 print "error: signature header size invalid"
149                 return 0
150
151         x = getvalue(x)
152         signature = x[0:3]
153         calibration = x[3]
154         print "signature: %.02x %.02x %.02x" % (x[0], x[1], x[2])
155         print "rc calibration: %.02x" % (x[3])
156
157         return 1
158
159 # get the fuse bytes
160 #
161 def fuse():
162         sendheader(2, 0)
163
164         x = getheader()
165         if x != 4:
166                 print "fuse failed: message count incorrect"
167                 return 0
168
169         x = getvalue(x)
170         print "fuse: %.02x %.02x %.02x %.02x" % (x[0], x[1], x[2], x[3])
171
172         return 1
173
174 # exit the BLS command loop
175 #
176 def doexit():
177         sendheader(7, 0)
178         x = getheader()
179         if x != 0:
180                 print "error: exit attempt failed"
181                 return 0
182
183         return 1
184
185 # initialize the SIM
186 #
187 def blsinit(p, b):
188         global s
189
190         # make sure that RST is tied to RESET on the AVR so we can force BLS to execute by driving RST low
191         s = serial.Serial(p, b, timeout=TIMEOUT, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, rtscts=0)
192         s.setRTS(1)
193         s.setDTR(1)
194         time.sleep(0.01)
195         s.flushInput()
196         s.setRTS(0)
197         s.setDTR(0)
198         time.sleep(0.01)
199
200         # based on a (presumed) bug in the AVR architecture, the BL wont
201         # accept USART input unless it first outputs a byte. Not sure why
202         # this occurs, but here is the compensating code:
203         x = s.read()
204         if x == None:
205                 print "error: no paramedics available"
206                 return 0
207         if x != '+':
208                 print "error: unexpected value: '+' != %.02x" % (ord(x))
209                 return 0
210
211         print "boot loader found. entering command mode."
212         s.write('?')
213
214         return 1
215
216 # upload to bootloader
217 #
218 def upload(p, d):
219         # send the address first
220         if addr(p) != 1:
221                 print "error: couldnt set page address"
222                 return 0
223
224         sendheader(4, len(d))
225
226         # ship the payload
227         v = 0
228         for i in d:
229                 v += i
230                 s.write(chr(i))
231
232         # twos complement the sum
233         v = (0x100 - v) & 0xff
234
235         x = getheader()
236         if x != 1:
237                 print "error: flash failed"
238                 return 0
239
240         x = getvalue(x)
241         if v != x[0]:
242                 print "error: checksums dont match: got=%.02x, expected=%.02x" % (x[0], v)
243                 return 0
244
245         return 1
246
247 # flash an image to the chip
248 #
249 def flash(f):
250         global psize
251
252         # make sure we have the page size first
253         pagesz()
254
255         # ingest the ihex file
256         h = IntelHex(f)
257
258         sys.stdout.write("flashing pages: ")
259
260         # always start with page zero; the code will auto-jump to
261         # the new address if there is no page zero in the hexfile
262         p = 0
263         d = []
264         n = -1
265         for a in h.addresses():
266                 # if the page has changed, upload
267                 if (a & ~((psize)-1)) != p:
268                         # upload only if we have data
269                         if len(d) > 0:
270                                 if upload(p, d) != 1:
271                                         print "error: upload failed"
272                                         return 0
273                                 sys.stdout.write(".")
274
275                         # set the new page
276                         p = (a & ~((psize)-1))
277
278                         # start a new list with this address
279                         d = []
280
281                         # reset the previous pointer
282                         n = -1
283
284                 # insert the data at the page address
285                 t = a & ((psize)-1)
286
287                 # if this address isn't an increment above last, fill the void
288                 while n + 1 < t:
289                         d.insert(n, 0xff)
290                         n += 1
291
292                 # ensure n reflects this byte
293                 n = t
294
295                 # insert the actual data byte
296                 d.insert(t, ord(h.gets(a, 1))) 
297
298         # if iterating through addresses() has completed but we still have data, 
299         # upload it now
300         if len(d) > 0:
301                 if upload(p, d) != 1:
302                         print "error: upload failed"
303                         return 0
304                 sys.stdout.write(".")
305
306         print ""
307
308         return 1
309
310 # flash an image from the web
311 #
312 def fromweb():
313         global signature
314
315         # get the device ID
316         if getsignature() != 1:
317                 print "error: can't retrieve chip signature"
318                 return 0
319
320         fn="/tmp/.goodfet.hex"
321         print "fromweb: retrieving image for chip %.02x.%.02x.%.02x" % (signature[0], signature[1], signature[2])
322         os.system("curl http://pa-ri.sc/goodfet/fw/%.02x%.02x%.02x.hex > %s" % (signature[0], signature[1], signature[2], fn))
323
324         return flash(fn)
325
326 # main
327 #
328 b = 500000
329 p = "/dev/ttyUSB0"
330 if len(sys.argv) > 1:
331         p = sys.argv[1]
332 if len(sys.argv) > 2:
333         b = sys.argv[2]
334
335 if blsinit(p, b) != 1:
336         print "error: couldnt initialize bls"
337         exit(1)
338
339 while 1:
340         sys.stdout.write("donbL> ")
341
342         c = sys.stdin.readline().rstrip()
343         c = re.split("[ \t]", c)
344
345         if c[0] == "exit":
346                 print "donbL: exiting"
347                 doexit()
348                 s.close()
349                 break
350
351         if c[0] == "peek":
352                 print "donbL: peeking address"
353                 peek(c[1])
354
355         if c[0] == "address":
356                 print "donbL: setting flash address"
357                 addr(c[1])
358
359         if c[0] == "pagesz":
360                 print "donbL: retrieving page size"
361                 pagesz()
362
363         if c[0] == "signature":
364                 print "donbL: retrieving avr signature"
365                 getsignature()
366
367         if c[0] == "fuse":
368                 print "donbL: retrieving avr fuse and lock bytes"
369                 fuse()
370
371         if c[0] == "flash":
372                 print "donbL: flashing image"
373                 flash(c[1])
374
375         if c[0] == "fromweb":
376                 print "donbL: flashing new image from web"
377                 fromweb()
378
379         if c[0] == "reset":
380                 s.close()
381                 blsinit(p, b)
382