works now
[gnuk/neug.git] / tool / dfuse.py
1 #! /usr/bin/python
2
3 """
4 dfuse.py - DFU (Device Firmware Upgrade) tool for STM32 Processor.
5 "SE" in DfuSe stands for "STmicroelectronics Extention".
6
7 Copyright (C) 2010 Free Software Initiative of Japan
8 Author: NIIBE Yutaka <gniibe@fsij.org>
9
10 This file is a part of Gnuk, a GnuPG USB Token implementation.
11
12 Gnuk is free software: you can redistribute it and/or modify it
13 under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 Gnuk is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
20 License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 """
25
26 from intel_hex import *
27 import sys, time, struct
28
29 # INPUT: intel hex file
30
31 # As of October 2010 (DfuSe V3.0.1 - 06/18/2010), it seems that
32 # following features are not supported by DfuSe implementation on
33 # target:
34 #
35 #     unprotect
36 #     leave_dfu_mode
37 #     write to option bytes
38 #     erase for mass erase
39
40
41 # See: AN3156 by STMicroelectronics
42
43 import usb
44
45 # check string descriptor in interrface descriptor in config descriptor: iInterface
46
47 # USB DFU class, subclass, protocol
48 DFU_CLASS = 0xFE
49 DFU_SUBCLASS = 0x01
50 DFU_STM32PROTOCOL_0 = 0
51 DFU_STM32PROTOCOL_2 = 2
52
53 # DFU request
54 DFU_DETACH    = 0x00
55 DFU_DNLOAD    = 0x01
56 DFU_UPLOAD    = 0x02
57 DFU_GETSTATUS = 0x03
58 DFU_CLRSTATUS = 0x04
59 DFU_GETSTATE  = 0x05
60 DFU_ABORT     = 0x06
61
62 # DFU status
63 DFU_STATUS_OK                 = 0x00
64 DFU_STATUS_ERROR_TARGET       = 0x01
65 DFU_STATUS_ERROR_FILE         = 0x02
66 DFU_STATUS_ERROR_WRITE        = 0x03
67 DFU_STATUS_ERROR_ERASE        = 0x04
68 DFU_STATUS_ERROR_CHECK_ERASED = 0x05
69 DFU_STATUS_ERROR_PROG         = 0x06
70 DFU_STATUS_ERROR_VERIFY       = 0x07
71 DFU_STATUS_ERROR_ADDRESS      = 0x08
72 DFU_STATUS_ERROR_NOTDONE      = 0x09
73 DFU_STATUS_ERROR_FIRMWARE     = 0x0a
74 DFU_STATUS_ERROR_VENDOR       = 0x0b
75 DFU_STATUS_ERROR_USBR         = 0x0c
76 DFU_STATUS_ERROR_POR          = 0x0d
77 DFU_STATUS_ERROR_UNKNOWN      = 0x0e
78 DFU_STATUS_ERROR_STALLEDPKT   = 0x0f
79
80 # DFU state
81 STATE_APP_IDLE                = 0x00
82 STATE_APP_DETACH              = 0x01
83 STATE_DFU_IDLE                = 0x02
84 STATE_DFU_DOWNLOAD_SYNC       = 0x03
85 STATE_DFU_DOWNLOAD_BUSY       = 0x04
86 STATE_DFU_DOWNLOAD_IDLE       = 0x05
87 STATE_DFU_MANIFEST_SYNC       = 0x06
88 STATE_DFU_MANIFEST            = 0x07
89 STATE_DFU_MANIFEST_WAIT_RESET = 0x08
90 STATE_DFU_UPLOAD_IDLE         = 0x09
91 STATE_DFU_ERROR               = 0x0a
92
93 # Return tuple of 4-bytes from integer
94 def get_four_bytes (v):
95     return [ v % 256, (v >> 8)%256, (v >> 16)%256, (v >> 24) ]
96
97 class DFU_STM32:
98     def __init__(self, device, configuration, interface):
99         """
100         __init__(device, configuration, interface) -> None
101         Initialize the device.
102         device: usb.Device object.
103         configuration: configuration number.
104         interface: usb.Interface object representing the interface and altenate setting.
105         """
106         if interface.interfaceClass != DFU_CLASS:
107             raise ValueError, "Wrong interface class"
108         if interface.interfaceSubClass != DFU_SUBCLASS:
109             raise ValueError, "Wrong interface sub class"
110         self.__protocol = interface.interfaceProtocol
111         self.__devhandle = device.open()
112         self.__devhandle.setConfiguration(configuration)
113         self.__devhandle.claimInterface(interface)
114         self.__devhandle.setAltInterface(interface)
115
116         self.__intf = interface.interfaceNumber
117         self.__alt = interface.alternateSetting
118         self.__conf = configuration
119         # Initialize members
120         self.__blocknum = 0
121
122     def ll_getdev(self):
123         return self.__devhandle
124
125     def ll_get_string(self, index):
126         # specify buffer length for 80
127         return self.__devhandle.getString(index, 80)
128
129     def ll_get_status(self):
130         # Status, PollTimeout[3], State, String
131         return self.__devhandle.controlMsg(requestType = 0xa1,
132                                            request = DFU_GETSTATUS,
133                                            value = 0,
134                                            index = self.__intf,
135                                            buffer = 6,
136                                            timeout = 3000000)
137
138     def ll_clear_status(self):
139         return self.__devhandle.controlMsg(requestType = 0x21,
140                                            request = DFU_CLRSTATUS,
141                                            value = 0,
142                                            index = self.__intf,
143                                            buffer = None)
144
145     # Upload: TARGET -> HOST
146     def ll_upload_block(self, block_num):
147         return self.__devhandle.controlMsg(requestType = 0xa1,
148                                            request = DFU_UPLOAD,
149                                            value = block_num,
150                                            index = self.__intf,
151                                            buffer = 1024,
152                                            timeout = 3000000)
153
154     # Download: HOST -> TARGET
155     def ll_download_block(self, block_num, block):
156         return self.__devhandle.controlMsg(requestType = 0x21,
157                                            request = DFU_DNLOAD,
158                                            value = block_num,
159                                            index = self.__intf,
160                                            buffer = block)
161
162     def dfuse_read_memory(self):
163         blocknum = self.__blocknum
164         self.__blocknum = self.__blocknum + 1
165         try:
166             block = self.ll_upload_block(blocknum)
167             return block
168         except:
169             s = self.ll_get_status()
170             while s[4] == STATE_DFU_DOWNLOAD_BUSY:
171                 time.sleep(0.1)
172                 s = self.ll_get_status()
173             raise ValueError, "Read memory failed (%d)" % s[0]
174
175     def dfuse_set_address_pointer(self, address):
176         bytes = get_four_bytes (address)
177         self.__blocknum = 2
178         self.ll_download_block(0, [0x21] + bytes)
179         s = self.ll_get_status()
180         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
181             time.sleep(0.1)
182             s = self.ll_get_status()
183         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
184             raise ValueError, "Set Address Pointer failed"
185
186     def dfuse_erase(self, address):
187         bytes = get_four_bytes (address)
188         self.ll_download_block(0, [0x41] + bytes)
189         s = self.ll_get_status()
190         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
191             time.sleep(0.1)
192             s = self.ll_get_status()
193         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
194             raise ValueError, "Erase failed"
195
196     def dfuse_write_memory(self, block):
197         blocknum = self.__blocknum
198         self.__blocknum = self.__blocknum + 1
199         self.ll_download_block(blocknum, block)
200         s = self.ll_get_status()
201         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
202             time.sleep(0.1)
203             s = self.ll_get_status()
204         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
205             raise ValueError, "Write memory failed"
206
207     def download(self, ih):
208         # First, erase pages
209         sys.stdout.write("Erasing: ")
210         sys.stdout.flush()
211         for start_addr in sorted(ih.memory.keys()):
212             data = ih.memory[start_addr]
213             end_addr = start_addr + len(data)
214             addr = start_addr & 0xfffffc00
215             i = 0
216             while addr < end_addr:
217                 self.dfuse_erase(addr)
218                 if i & 0x03 == 0x03:
219                     sys.stdout.write("#")
220                     sys.stdout.flush()
221                 addr += 1024
222                 i += 1
223         sys.stdout.write("\n")
224         sys.stdout.flush()
225         # Then, write pages
226         sys.stdout.write("Writing: ")
227         sys.stdout.flush()
228         for start_addr in sorted(ih.memory.keys()):
229             data = ih.memory[start_addr]
230             end_addr = start_addr + len(data)
231             addr = start_addr & 0xfffffc00
232             # XXX: data should be 1-KiB aligned
233             if addr != start_addr:
234                 raise ValueError, "padding is not supported yet"
235             self.dfuse_set_address_pointer(addr)
236             i = 0
237             while addr < end_addr:
238                 self.dfuse_write_memory(data[i*1024:(i+1)*1024])
239                 if i & 0x03 == 0x03:
240                     sys.stdout.write("#")
241                     sys.stdout.flush()
242                 addr += 1024
243                 i += 1
244         if self.__protocol == DFU_STM32PROTOCOL_0:
245             # 0-length write at the end
246             self.ll_download_block(self.__blocknum, None)
247             s = self.ll_get_status()
248             if s[4] == STATE_DFU_MANIFEST:
249                 time.sleep(1)
250                 try:
251                     s = self.ll_get_status()
252                 except:
253                     self.__devhandle.reset()
254             elif s[4] == STATE_DFU_MANIFEST_WAIT_RESET:
255                 self.__devhandle.reset()
256             elif s[4] != STATE_DFU_IDLE:
257                 raise ValueError, "write failed (%d)." % s[4]
258         else:
259             self.ll_clear_status()
260             self.ll_clear_status()
261         sys.stdout.write("\n")
262         sys.stdout.flush()
263
264     def verify(self, ih):
265         s = self.ll_get_status()
266         if s[4] != STATE_DFU_IDLE:
267             self.ll_clear_status()
268         # Read pages
269         sys.stdout.write("Reading: ")
270         sys.stdout.flush()
271         for start_addr in sorted(ih.memory.keys()):
272             data = ih.memory[start_addr]
273             end_addr = start_addr + len(data)
274             addr = start_addr & 0xfffffc00
275             # XXX: data should be 1-KiB aligned
276             if addr != start_addr:
277                 raise ValueError, "padding is not supported yet"
278             self.dfuse_set_address_pointer(addr)
279             self.ll_clear_status()
280             self.ll_clear_status()
281             i = 0
282             while addr < end_addr:
283                 block = self.dfuse_read_memory()
284                 j = 0
285                 for c in data[i*1024:(i+1)*1024]:
286                     if (ord(c)&0xff) != block[j]:
287                         raise ValueError, "verify failed at %08x" % (addr + i*1024+j)
288                     j += 1
289                 if i & 0x03 == 0x03:
290                     sys.stdout.write("#")
291                     sys.stdout.flush()
292                 addr += 1024
293                 i += 1
294             self.ll_clear_status()
295             self.ll_clear_status()
296         self.ll_clear_status()
297         sys.stdout.write("\n")
298         sys.stdout.flush()
299
300 busses = usb.busses()
301
302 # 0483: SGS Thomson Microelectronics
303 # df11: DfuSe
304 USB_VENDOR_STMICRO=0x0483
305 USB_PRODUCT_DFUSE=0xdf11
306
307 def get_device():
308     for bus in busses:
309         devices = bus.devices
310         for dev in devices:
311             if dev.idVendor != USB_VENDOR_STMICRO:
312                 continue
313             if dev.idProduct != USB_PRODUCT_DFUSE:
314                 continue
315             for config in dev.configurations:
316                 for intf in config.interfaces:
317                     for alt in intf:
318                         if alt.interfaceClass == DFU_CLASS and \
319                                 alt.interfaceSubClass == DFU_SUBCLASS and \
320                                 (alt.interfaceProtocol == DFU_STM32PROTOCOL_0 or \
321                                      alt.interfaceProtocol == DFU_STM32PROTOCOL_2):
322                             return dev, config, alt
323     raise ValueError, "Device not found"
324
325 def main(filename):
326     dev, config, intf = get_device()
327     print "Device:", dev.filename
328     print "Configuration", config.value
329     print "Interface", intf.interfaceNumber
330     dfu = DFU_STM32(dev, config, intf)
331     print dfu.ll_get_string(intf.iInterface)
332     s = dfu.ll_get_status()
333     if s[4] == STATE_DFU_ERROR:
334         dfu.ll_clear_status()
335     s = dfu.ll_get_status()
336     print s
337     if s[4] == STATE_DFU_IDLE:
338         exit
339     transfer_size = 1024
340     if s[0] != DFU_STATUS_OK:
341         print s
342         exit
343     ih = intel_hex(filename)
344     dfu.download(ih)
345     dfu.verify(ih)
346
347 if __name__ == '__main__':
348     main(sys.argv[1])