IEEE 802.15.4 subsystem
Sign in or create your account | Project List | Help
IEEE 802.15.4 subsystem Git Source Tree
Root/
| 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 | } |
| 261 |
