c84a28dfcef8847026432ab9f213a15d2fe7238d
[linux-libre-firmware.git] / atusb / usb / dfu.c
1 /*
2  * boot/dfu.c - DFU protocol engine
3  *
4  * Written 2008-2011, 2013-2015 by Werner Almesberger
5  * Copyright 2008-2011, 2013-2015 Werner Almesberger
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  */
12
13 /*
14  * http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf
15  */
16
17 /*
18  * A few, erm, shortcuts:
19  *
20  * - we don't bother with the app* states since DFU is all this firmware does
21  * - after DFU_DNLOAD, we just block until things are written, so we never
22  *   enter dfuDNLOAD_SYNC or dfuDNBUSY
23  * - no dfuMANIFEST_SYNC, dfuMANIFEST, or dfuMANIFEST_WAIT_RESET
24  * - to keep our buffers small, we only accept EP0-sized blocks
25  */
26
27
28 #include <stdbool.h>
29 #include <stdint.h>
30
31 #include "usb.h"
32 #include "dfu.h"
33
34 #include "board.h"
35
36
37 #ifndef NULL
38 #define NULL 0
39 #endif
40
41 #define debug(...)
42 #define error(...)
43
44
45 #ifndef DFU_ALT_SETTINGS
46 #define DFU_ALT_SETTINGS        1
47 #endif
48
49 #ifndef DFU_ALT_NAME_0_IDX
50 #define DFU_ALT_NAME_0_IDX      0
51 #endif
52
53 #ifndef DFU_ALT_NAME_1_IDX
54 #define DFU_ALT_NAME_1_IDX      0
55 #endif
56
57 #ifndef DFU_ALT_NAME_2_IDX
58 #define DFU_ALT_NAME_2_IDX      0
59 #endif
60
61
62 const uint8_t device_descriptor[] = {
63         18,                     /* bLength */
64         USB_DT_DEVICE,          /* bDescriptorType */
65         LE(0x100),              /* bcdUSB */
66         USB_CLASS_APP_SPEC,     /* bDeviceClass */
67         0x00,                   /* bDeviceSubClass (per interface) */
68         0x00,                   /* bDeviceProtocol (per interface) */
69         EP0_SIZE,               /* bMaxPacketSize */
70         LE(DFU_USB_VENDOR),     /* idVendor */
71         LE(DFU_USB_PRODUCT),    /* idProduct */
72         LE(0x0001),             /* bcdDevice */
73         0,                      /* iManufacturer */
74         0,                      /* iProduct */
75 #ifdef HAS_BOARD_SERNUM
76         1,                      /* iSerialNumber */
77 #else
78         0,                      /* iSerialNumber */
79 #endif
80         1                       /* bNumConfigurations */
81 };
82
83
84 const uint8_t config_descriptor[] = {
85         9,                      /* bLength */
86         USB_DT_CONFIG,          /* bDescriptorType */
87         LE(9+9*DFU_ALT_SETTINGS), /* wTotalLength */
88         1,                      /* bNumInterfaces */
89         1,                      /* bConfigurationValue (> 0 !) */
90         0,                      /* iConfiguration */
91 //      USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED,
92         USB_ATTR_BUS_POWERED,   /* bmAttributes */
93         ((BOARD_MAX_mA)+1)/2,   /* bMaxPower */
94
95         /* Interface #0 */
96
97         DFU_ITF_DESCR(0, 0, dfu_proto_dfu, DFU_ALT_NAME_0_IDX)
98 #if DFU_ALT_SETTINGS > 1
99         DFU_ITF_DESCR(0, 1, dfu_proto_dfu, DFU_ALT_NAME_1_IDX)
100 #endif
101 #if DFU_ALT_SETTINGS > 2
102         DFU_ITF_DESCR(0, 2, dfu_proto_dfu, DFU_ALT_NAME_2_IDX)
103 #endif
104 };
105
106
107 static uint16_t next_block = 0;
108 static bool did_download;
109
110
111 static uint8_t buf[EP0_SIZE];
112
113
114 static void block_write(void *user)
115 {
116         uint16_t *size = user;
117
118         dfu_flash_ops->write(buf, *size);
119 }
120
121
122 static bool block_receive(uint16_t length)
123 {
124         static uint16_t size;
125
126         if (!dfu_flash_ops->can_write(length)) {
127                 dfu.state = dfuERROR;   
128                 dfu.status = errADDRESS;
129                 return 0;
130         }
131         if (length > EP0_SIZE) {
132                 dfu.state = dfuERROR;   
133                 dfu.status = errUNKNOWN;
134                 return 0;
135         }
136         size = length;
137         usb_recv(&eps[0], buf, size, block_write, &size);
138         return 1;
139 }
140
141
142 static bool block_transmit(uint16_t length)
143 {
144         uint16_t got;
145
146         if (length > EP0_SIZE) {
147                 dfu.state = dfuERROR;   
148                 dfu.status = errUNKNOWN;
149                 return 1;
150         }
151         got = dfu_flash_ops->read(buf, length);
152         if (got < length) {
153                 length = got;
154                 dfu.state = dfuIDLE;
155         }
156         usb_send(&eps[0], buf, length, NULL, NULL);
157         return 1;
158 }
159
160
161 static bool my_setup(const struct setup_request *setup)
162 {
163         bool ok;
164
165         switch (setup->bmRequestType | setup->bRequest << 8) {
166         case DFU_TO_DEV(DFU_DETACH):
167                 debug("DFU_DETACH\n");
168                 /*
169                  * The DFU spec says thay this is sent in protocol 1 only.
170                  * However, dfu-util also sends it to get out of DFU mode,
171                  * so we just don't make a fuss and ignore it.
172                  */
173                 return 1;
174         case DFU_TO_DEV(DFU_DNLOAD):
175                 debug("DFU_DNLOAD\n");
176                 if (dfu.state == dfuIDLE) {
177                         next_block = setup->wValue;
178                         dfu_flash_ops->start();
179                 }
180                 else if (dfu.state != dfuDNLOAD_IDLE) {
181                         error("bad state\n");
182                         return 0;
183                 }
184                 if (dfu.state != dfuIDLE && setup->wValue == next_block-1) {
185                         debug("retransmisson\n");
186                         return 1;
187                 }
188                 if (setup->wValue != next_block) {
189                         debug("bad block (%d vs. %d)\n",
190                             setup->wValue, next_block);
191                         dfu.state = dfuERROR;
192                         dfu.status = errUNKNOWN;
193                         return 1;
194                 }
195                 if (!setup->wLength) {
196                         debug("DONE\n");
197                         dfu_flash_ops->end_write();
198                         dfu.state = dfuIDLE;
199                         did_download = 1;
200                         return 1;
201                 }
202                 ok = block_receive(setup->wLength);
203                 next_block++;
204                 dfu.state = dfuDNLOAD_IDLE;
205                 return ok;
206         case DFU_FROM_DEV(DFU_UPLOAD):
207                 debug("DFU_UPLOAD\n");
208                 if (dfu.state == dfuIDLE) {
209                         next_block = setup->wValue;
210                         dfu_flash_ops->start();
211                 }
212                 else if (dfu.state != dfuUPLOAD_IDLE)
213                         return 0;
214                 if (dfu.state != dfuIDLE && setup->wValue == next_block-1) {
215                         debug("retransmisson\n");
216                         /* @@@ try harder */
217                         dfu.state = dfuERROR;
218                         dfu.status = errUNKNOWN;
219                         return 1;
220                 }
221                 if (setup->wValue != next_block) {
222                         debug("bad block (%d vs. %d)\n",
223                             setup->wValue, next_block);
224                         dfu.state = dfuERROR;
225                         dfu.status = errUNKNOWN;
226                         return 1;
227                 }
228                 ok = block_transmit(setup->wLength);
229                 next_block++;
230                 dfu.state = dfuUPLOAD_IDLE;
231                 return ok;
232         case DFU_TO_DEV(DFU_ABORT):
233                 debug("DFU_ABORT\n");
234                 dfu.state = dfuIDLE;
235                 dfu.status = OK;
236                 return 1;
237         default:
238                 return dfu_setup_common(setup);
239         }
240 }
241
242
243 static void my_reset(void)
244 {
245 #if 0
246         /* @@@ not nice -- think about where this should go */
247         extern void run_payload(void);
248
249         if (did_download)
250                 run_payload();
251 #endif
252 }
253
254
255 void dfu_init(void)
256 {
257         user_setup = my_setup;
258         user_get_descriptor = dfu_my_descr;
259         user_reset = my_reset;
260 }