2 * boot/dfu.c - DFU protocol engine
4 * Written 2008-2011, 2013-2015 by Werner Almesberger
5 * Copyright 2008-2011, 2013-2015 Werner Almesberger
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.
14 * http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf
18 * A few, erm, shortcuts:
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
45 #ifndef DFU_ALT_SETTINGS
46 #define DFU_ALT_SETTINGS 1
49 #ifndef DFU_ALT_NAME_0_IDX
50 #define DFU_ALT_NAME_0_IDX 0
53 #ifndef DFU_ALT_NAME_1_IDX
54 #define DFU_ALT_NAME_1_IDX 0
57 #ifndef DFU_ALT_NAME_2_IDX
58 #define DFU_ALT_NAME_2_IDX 0
62 const uint8_t device_descriptor[] = {
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 */
75 #ifdef HAS_BOARD_SERNUM
76 1, /* iSerialNumber */
78 0, /* iSerialNumber */
80 1 /* bNumConfigurations */
84 const uint8_t config_descriptor[] = {
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 */
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)
101 #if DFU_ALT_SETTINGS > 2
102 DFU_ITF_DESCR(0, 2, dfu_proto_dfu, DFU_ALT_NAME_2_IDX)
107 static uint16_t next_block = 0;
108 static bool did_download;
111 static uint8_t buf[EP0_SIZE];
114 static void block_write(void *user)
116 uint16_t *size = user;
118 dfu_flash_ops->write(buf, *size);
122 static bool block_receive(uint16_t length)
124 static uint16_t size;
126 if (!dfu_flash_ops->can_write(length)) {
127 dfu.state = dfuERROR;
128 dfu.status = errADDRESS;
131 if (length > EP0_SIZE) {
132 dfu.state = dfuERROR;
133 dfu.status = errUNKNOWN;
137 usb_recv(&eps[0], buf, size, block_write, &size);
142 static bool block_transmit(uint16_t length)
146 if (length > EP0_SIZE) {
147 dfu.state = dfuERROR;
148 dfu.status = errUNKNOWN;
151 got = dfu_flash_ops->read(buf, length);
156 usb_send(&eps[0], buf, length, NULL, NULL);
161 static bool my_setup(const struct setup_request *setup)
165 switch (setup->bmRequestType | setup->bRequest << 8) {
166 case DFU_TO_DEV(DFU_DETACH):
167 debug("DFU_DETACH\n");
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.
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();
180 else if (dfu.state != dfuDNLOAD_IDLE) {
181 error("bad state\n");
184 if (dfu.state != dfuIDLE && setup->wValue == next_block-1) {
185 debug("retransmisson\n");
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;
195 if (!setup->wLength) {
197 dfu_flash_ops->end_write();
202 ok = block_receive(setup->wLength);
204 dfu.state = dfuDNLOAD_IDLE;
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();
212 else if (dfu.state != dfuUPLOAD_IDLE)
214 if (dfu.state != dfuIDLE && setup->wValue == next_block-1) {
215 debug("retransmisson\n");
217 dfu.state = dfuERROR;
218 dfu.status = errUNKNOWN;
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;
228 ok = block_transmit(setup->wLength);
230 dfu.state = dfuUPLOAD_IDLE;
232 case DFU_TO_DEV(DFU_ABORT):
233 debug("DFU_ABORT\n");
238 return dfu_setup_common(setup);
243 static void my_reset(void)
246 /* @@@ not nice -- think about where this should go */
247 extern void run_payload(void);
257 user_setup = my_setup;
258 user_get_descriptor = dfu_my_descr;
259 user_reset = my_reset;