ba1293f6d966891ede94c21217425b5b56d40cf6
[gnuk/gnuk.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 __del__(self):
123         try:
124             self.__devhandle.releaseInterface(self.__intf)
125             del self.__devhandle
126         except:
127             pass
128
129     def ll_getdev(self):
130         return self.__devhandle
131
132     def ll_get_string(self, index):
133         # specify buffer length for 80
134         return self.__devhandle.getString(index, 80)
135
136     def ll_get_status(self):
137         # Status, PollTimeout[3], State, String
138         return self.__devhandle.controlMsg(requestType = 0xa1,
139                                            request = DFU_GETSTATUS,
140                                            value = 0,
141                                            index = self.__intf,
142                                            buffer = 6,
143                                            timeout = 3000000)
144
145     def ll_clear_status(self):
146         return self.__devhandle.controlMsg(requestType = 0x21,
147                                            request = DFU_CLRSTATUS,
148                                            value = 0,
149                                            index = self.__intf,
150                                            buffer = None)
151
152     # Upload: TARGET -> HOST
153     def ll_upload_block(self, block_num):
154         return self.__devhandle.controlMsg(requestType = 0xa1,
155                                            request = DFU_UPLOAD,
156                                            value = block_num,
157                                            index = self.__intf,
158                                            buffer = 1024,
159                                            timeout = 3000000)
160
161     # Download: HOST -> TARGET
162     def ll_download_block(self, block_num, block):
163         return self.__devhandle.controlMsg(requestType = 0x21,
164                                            request = DFU_DNLOAD,
165                                            value = block_num,
166                                            index = self.__intf,
167                                            buffer = block)
168
169     def dfuse_read_memory(self):
170         blocknum = self.__blocknum
171         self.__blocknum = self.__blocknum + 1
172         try:
173             block = self.ll_upload_block(blocknum)
174             return block
175         except:
176             s = self.ll_get_status()
177             while s[4] == STATE_DFU_DOWNLOAD_BUSY:
178                 time.sleep(0.1)
179                 s = self.ll_get_status()
180             raise ValueError, "Read memory failed (%d)" % s[0]
181
182     def dfuse_set_address_pointer(self, address):
183         bytes = get_four_bytes (address)
184         self.__blocknum = 2
185         self.ll_download_block(0, [0x21] + bytes)
186         s = self.ll_get_status()
187         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
188             time.sleep(0.1)
189             s = self.ll_get_status()
190         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
191             raise ValueError, "Set Address Pointer failed"
192
193     def dfuse_erase(self, address):
194         bytes = get_four_bytes (address)
195         self.ll_download_block(0, [0x41] + bytes)
196         s = self.ll_get_status()
197         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
198             time.sleep(0.1)
199             s = self.ll_get_status()
200         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
201             raise ValueError, "Erase failed"
202
203     def dfuse_write_memory(self, block):
204         blocknum = self.__blocknum
205         self.__blocknum = self.__blocknum + 1
206         self.ll_download_block(blocknum, block)
207         s = self.ll_get_status()
208         while s[4] == STATE_DFU_DOWNLOAD_BUSY:
209             time.sleep(0.1)
210             s = self.ll_get_status()
211         if s[4] != STATE_DFU_DOWNLOAD_IDLE:
212             raise ValueError, "Write memory failed"
213
214     def download(self, ih):
215         # First, erase pages
216         sys.stdout.write("Erasing: ")
217         sys.stdout.flush()
218         for start_addr in sorted(ih.memory.keys()):
219             data = ih.memory[start_addr]
220             end_addr = start_addr + len(data)
221             addr = start_addr & 0xfffffc00
222             i = 0
223             while addr < end_addr:
224                 self.dfuse_erase(addr)
225                 if i & 0x03 == 0x03:
226                     sys.stdout.write("#")
227                     sys.stdout.flush()
228                 addr += 1024
229                 i += 1
230         sys.stdout.write("\n")
231         sys.stdout.flush()
232         # Then, write pages
233         sys.stdout.write("Writing: ")
234         sys.stdout.flush()
235         for start_addr in sorted(ih.memory.keys()):
236             data = ih.memory[start_addr]
237             end_addr = start_addr + len(data)
238             addr = start_addr & 0xfffffc00
239             # XXX: data should be 1-KiB aligned and size should be just KiB.
240             if addr != start_addr:
241                 raise ValueError, "padding is not supported yet"
242             self.dfuse_set_address_pointer(addr)
243             i = 0
244             while addr < end_addr:
245                 self.dfuse_write_memory(data[i*1024:(i+1)*1024])
246                 if i & 0x03 == 0x03:
247                     sys.stdout.write("#")
248                     sys.stdout.flush()
249                 addr += 1024
250                 i += 1
251         if self.__protocol == DFU_STM32PROTOCOL_0:
252             # 0-length write at the end
253             self.ll_download_block(self.__blocknum, None)
254             s = self.ll_get_status()
255             if s[4] == STATE_DFU_MANIFEST:
256                 time.sleep(1)
257                 try:
258                     s = self.ll_get_status()
259                 except:
260                     self.__devhandle.reset()
261             elif s[4] == STATE_DFU_MANIFEST_WAIT_RESET:
262                 self.__devhandle.reset()
263             elif s[4] != STATE_DFU_IDLE:
264                 raise ValueError, "write failed (%d)." % s[4]
265         else:
266             self.ll_clear_status()
267             self.ll_clear_status()
268         sys.stdout.write("\n")
269         sys.stdout.flush()
270
271     def verify(self, ih):
272         s = self.ll_get_status()
273         if s[4] != STATE_DFU_IDLE:
274             self.ll_clear_status()
275         # Read pages
276         sys.stdout.write("Reading: ")
277         sys.stdout.flush()
278         for start_addr in sorted(ih.memory.keys()):
279             data = ih.memory[start_addr]
280             end_addr = start_addr + len(data)
281             addr = start_addr & 0xfffffc00
282             # XXX: data should be 1-KiB aligned and size should be just KiB.
283             if addr != start_addr:
284                 raise ValueError, "padding is not supported yet"
285             self.dfuse_set_address_pointer(addr)
286             self.ll_clear_status()
287             self.ll_clear_status()
288             i = 0
289             while addr < end_addr:
290                 block = self.dfuse_read_memory()
291                 j = 0
292                 for d in block:
293                     if d != (ord(data[i*1024+j])&0xff):
294                         raise ValueError, "verify failed at %08x" % (addr + i*1024+j)
295                     j += 1
296                 if i & 0x03 == 0x03:
297                     sys.stdout.write("#")
298                     sys.stdout.flush()
299                 addr += 1024
300                 i += 1
301         self.ll_clear_status()
302         sys.stdout.write("\n")
303         sys.stdout.flush()
304
305 busses = usb.busses()
306
307 def get_device():
308     for bus in busses:
309         devices = bus.devices
310         for dev in devices:
311             for config in dev.configurations:
312                 for intf in config.interfaces:
313                     for alt in intf:
314                         if alt.interfaceClass == DFU_CLASS and \
315                                 alt.interfaceSubClass == DFU_SUBCLASS and \
316                                 (alt.interfaceProtocol == DFU_STM32PROTOCOL_0 or \
317                                      alt.interfaceProtocol == DFU_STM32PROTOCOL_2):
318                             return dev, config, alt
319     raise ValueError, "Device not found"
320
321 def main(filename):
322     dev, config, intf = get_device()
323     print "Device:", dev.filename
324     print "Configuration", config.value
325     print "Interface", intf.interfaceNumber
326     dfu = DFU_STM32(dev, config, intf)
327     print dfu.ll_get_string(intf.iInterface)
328     s = dfu.ll_get_status()
329     if s[4] == STATE_DFU_ERROR:
330         dfu.ll_clear_status()
331     s = dfu.ll_get_status()
332     print s
333     if s[4] == STATE_DFU_IDLE:
334         exit
335     transfer_size = 1024
336     if s[0] != DFU_STATUS_OK:
337         print s
338         exit
339     ih = intel_hex(filename)
340     dfu.download(ih)
341     dfu.verify(ih)
342
343 if __name__ == '__main__':
344     main(sys.argv[1])