atusb/fw/usb/dfu.c |
| 1 | /* |
| 2 | * boot/dfu.c - DFU protocol engine |
| 3 | * |
| 4 | * Written 2008-2010 by Werner Almesberger |
| 5 | * Copyright 2008-2010 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 <stdint.h> |
| 29 | |
| 30 | #include "regs.h" |
| 31 | #include "uart.h" |
| 32 | #include "usb.h" |
| 33 | #include "dfu.h" |
| 34 | |
| 35 | #include "config.h" |
| 36 | |
| 37 | |
| 38 | #ifndef NULL |
| 39 | #define NULL 0 |
| 40 | #endif |
| 41 | |
| 42 | |
| 43 | #define PAYLOAD_END (PAYLOAD_START+PAYLOAD_SIZE) |
| 44 | |
| 45 | |
| 46 | const uint8_t device_descriptor[] = { |
| 47 | 18, /* bLength */ |
| 48 | USB_DT_DEVICE, /* bDescriptorType */ |
| 49 | LE(0x100), /* bcdUSB */ |
| 50 | USB_CLASS_PER_INTERFACE,/* bDeviceClass */ |
| 51 | 0x00, /* bDeviceSubClass (per interface) */ |
| 52 | 0x00, /* bDeviceProtocol (per interface) */ |
| 53 | EP0_SIZE, /* bMaxPacketSize */ |
| 54 | LE(USB_VENDOR), /* idVendor */ |
| 55 | LE(USB_PRODUCT), /* idProduct */ |
| 56 | LE(0x0001), /* bcdDevice */ |
| 57 | 0, /* iManufacturer */ |
| 58 | 0, /* iProduct */ |
| 59 | 0, /* iSerialNumber */ |
| 60 | 1 /* bNumConfigurations */ |
| 61 | }; |
| 62 | |
| 63 | |
| 64 | const uint8_t config_descriptor[] = { |
| 65 | 9, /* bLength */ |
| 66 | USB_DT_CONFIG, /* bDescriptorType */ |
| 67 | LE(9+9), /* wTotalLength */ |
| 68 | 1, /* bNumInterfaces */ |
| 69 | 1, /* bConfigurationValue (> 0 !) */ |
| 70 | 0, /* iConfiguration */ |
| 71 | // USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED, |
| 72 | USB_ATTR_BUS_POWERED, /* bmAttributes */ |
| 73 | 15, /* bMaxPower */ |
| 74 | |
| 75 | /* Interface #0 */ |
| 76 | |
| 77 | 9, /* bLength */ |
| 78 | USB_DT_INTERFACE, /* bDescriptorType */ |
| 79 | 0, /* bInterfaceNumber */ |
| 80 | 0, /* bAlternateSetting */ |
| 81 | 0, /* bNumEndpoints */ |
| 82 | 0xfe, /* bInterfaceClass (application specific) */ |
| 83 | 0x01, /* bInterfaceSubClass (device fw upgrade) */ |
| 84 | 0x02, /* bInterfaceProtocol (DFU mode protocol) */ |
| 85 | 0, /* iInterface */ |
| 86 | }; |
| 87 | |
| 88 | |
| 89 | static const uint8_t functional_descriptor[] = { |
| 90 | 9, /* bLength */ |
| 91 | DFU_DT_FUNCTIONAL, /* bDescriptorType */ |
| 92 | 0xf, /* bmAttributes (claim omnipotence :-) */ |
| 93 | LE(0xffff), /* wDetachTimeOut (we're very patient) */ |
| 94 | LE(EP0_SIZE), /* wTransferSize */ |
| 95 | LE(0x101), /* bcdDFUVersion */ |
| 96 | }; |
| 97 | |
| 98 | |
| 99 | struct dfu dfu = { |
| 100 | OK, |
| 101 | LE(1000), 0, |
| 102 | dfuIDLE, |
| 103 | 0, |
| 104 | }; |
| 105 | |
| 106 | |
| 107 | static uint16_t next_block = 0; |
| 108 | static uint16_t payload; |
| 109 | static __bit did_download; |
| 110 | |
| 111 | |
| 112 | static __xdata uint8_t buf[EP0_SIZE]; |
| 113 | |
| 114 | |
| 115 | static void flash_erase_page(uint16_t addr) |
| 116 | { |
| 117 | FLKEY = 0xa5; |
| 118 | FLKEY = 0xf1; |
| 119 | PSCTL |= PSEE; |
| 120 | PSCTL |= PSWE; |
| 121 | *(__xdata uint8_t *) addr = 0; |
| 122 | PSCTL &= ~PSWE; |
| 123 | PSCTL &= ~PSEE; |
| 124 | } |
| 125 | |
| 126 | |
| 127 | static void flash_write_byte(uint16_t addr, uint8_t value) |
| 128 | { |
| 129 | FLKEY = 0xa5; |
| 130 | FLKEY = 0xf1; |
| 131 | PSCTL |= PSWE; |
| 132 | PSCTL &= ~PSEE; |
| 133 | *(__xdata uint8_t *) addr = value; |
| 134 | PSCTL &= ~PSWE; |
| 135 | } |
| 136 | |
| 137 | |
| 138 | static void block_write(void *user) |
| 139 | { |
| 140 | uint16_t *size = user; |
| 141 | uint8_t *p; |
| 142 | |
| 143 | for (p = buf; p != buf+*size; p++) { |
| 144 | if (!(payload & 511)) |
| 145 | flash_erase_page(payload); |
| 146 | flash_write_byte(payload, *p); |
| 147 | payload++; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | |
| 152 | static __bit block_receive(uint16_t length) |
| 153 | { |
| 154 | static uint16_t size; |
| 155 | |
| 156 | if (payload < PAYLOAD_START || payload+length > PAYLOAD_END) { |
| 157 | dfu.state = dfuERROR; |
| 158 | dfu.status = errADDRESS; |
| 159 | return 0; |
| 160 | } |
| 161 | if (length > EP0_SIZE) { |
| 162 | dfu.state = dfuERROR; |
| 163 | dfu.status = errUNKNOWN; |
| 164 | return 0; |
| 165 | } |
| 166 | size = length; |
| 167 | usb_recv(&ep0, buf, size, block_write, &size); |
| 168 | return 1; |
| 169 | } |
| 170 | |
| 171 | |
| 172 | static __bit block_transmit(uint16_t length) |
| 173 | { |
| 174 | uint16_t left; |
| 175 | |
| 176 | if (payload < PAYLOAD_START || payload > PAYLOAD_END) { |
| 177 | dfu.state = dfuERROR; |
| 178 | dfu.status = errADDRESS; |
| 179 | return 1; |
| 180 | } |
| 181 | if (length > EP0_SIZE) { |
| 182 | dfu.state = dfuERROR; |
| 183 | dfu.status = errUNKNOWN; |
| 184 | return 1; |
| 185 | } |
| 186 | left = PAYLOAD_END-payload; |
| 187 | if (left < length) { |
| 188 | length = left; |
| 189 | dfu.state = dfuIDLE; |
| 190 | } |
| 191 | usb_send(&ep0, (__code uint8_t *) payload, length, NULL, NULL); |
| 192 | payload += length; |
| 193 | return 1; |
| 194 | } |
| 195 | |
| 196 | |
| 197 | static __bit my_setup(struct setup_request *setup) __reentrant |
| 198 | { |
| 199 | __bit ok; |
| 200 | |
| 201 | switch (setup->bmRequestType | setup->bRequest << 8) { |
| 202 | case DFU_TO_DEV(DFU_DETACH): |
| 203 | debug("DFU_DETACH\n"); |
| 204 | /* |
| 205 | * The DFU spec says thay this is sent in protocol 1 only. |
| 206 | * However, dfu-util also sends it to get out of DFU mode, |
| 207 | * so we just don't make a fuss and ignore it. |
| 208 | */ |
| 209 | return 1; |
| 210 | case DFU_TO_DEV(DFU_DNLOAD): |
| 211 | debug("DFU_DNLOAD\n"); |
| 212 | if (dfu.state == dfuIDLE) { |
| 213 | next_block = setup->wValue; |
| 214 | payload = PAYLOAD_START; |
| 215 | } |
| 216 | else if (dfu.state != dfuDNLOAD_IDLE) { |
| 217 | error("bad state\n"); |
| 218 | return 0; |
| 219 | } |
| 220 | if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { |
| 221 | debug("retransmisson\n"); |
| 222 | return 1; |
| 223 | } |
| 224 | if (setup->wValue != next_block) { |
| 225 | debug("bad block (%d vs. %d)\n", |
| 226 | setup->wValue, next_block); |
| 227 | dfu.state = dfuERROR; |
| 228 | dfu.status = errUNKNOWN; |
| 229 | return 1; |
| 230 | } |
| 231 | if (!setup->wLength) { |
| 232 | debug("DONE\n"); |
| 233 | dfu.state = dfuIDLE; |
| 234 | did_download = 1; |
| 235 | return 1; |
| 236 | } |
| 237 | ok = block_receive(setup->wLength); |
| 238 | next_block++; |
| 239 | dfu.state = dfuDNLOAD_IDLE; |
| 240 | return ok; |
| 241 | case DFU_FROM_DEV(DFU_UPLOAD): |
| 242 | debug("DFU_UPLOAD\n"); |
| 243 | if (dfu.state == dfuIDLE) { |
| 244 | next_block = setup->wValue; |
| 245 | payload = PAYLOAD_START; |
| 246 | } |
| 247 | else if (dfu.state != dfuUPLOAD_IDLE) |
| 248 | return 0; |
| 249 | if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { |
| 250 | debug("retransmisson\n"); |
| 251 | /* @@@ try harder */ |
| 252 | dfu.state = dfuERROR; |
| 253 | dfu.status = errUNKNOWN; |
| 254 | return 1; |
| 255 | } |
| 256 | if (setup->wValue != next_block) { |
| 257 | debug("bad block (%d vs. %d)\n", |
| 258 | setup->wValue, next_block); |
| 259 | dfu.state = dfuERROR; |
| 260 | dfu.status = errUNKNOWN; |
| 261 | return 1; |
| 262 | } |
| 263 | ok = block_transmit(setup->wLength); |
| 264 | next_block++; |
| 265 | dfu.state = dfuUPLOAD_IDLE; |
| 266 | return ok; |
| 267 | case DFU_FROM_DEV(DFU_GETSTATUS): |
| 268 | debug("DFU_GETSTATUS\n"); |
| 269 | usb_send(&ep0, (uint8_t *) &dfu, sizeof(dfu), NULL, NULL); |
| 270 | return 1; |
| 271 | case DFU_TO_DEV(DFU_CLRSTATUS): |
| 272 | debug("DFU_CLRSTATUS\n"); |
| 273 | dfu.state = dfuIDLE; |
| 274 | dfu.status = OK; |
| 275 | return 1; |
| 276 | case DFU_FROM_DEV(DFU_GETSTATE): |
| 277 | debug("DFU_GETSTATE\n"); |
| 278 | usb_send(&ep0, &dfu.state, 1, NULL, NULL); |
| 279 | return 1; |
| 280 | case DFU_TO_DEV(DFU_ABORT): |
| 281 | debug("DFU_ABORT\n"); |
| 282 | dfu.state = dfuIDLE; |
| 283 | dfu.status = OK; |
| 284 | return 1; |
| 285 | default: |
| 286 | #ifdef CONFIG_PRINTK |
| 287 | printk("DFU rt %x, rq%x ?\n", |
| 288 | setup->bmRequestType, setup->bRequest); |
| 289 | #else |
| 290 | /* |
| 291 | * @@@ SDCC 2.7.0 ends up OR'in setup->bmRequestType with |
| 292 | * setup->bRequest unshifted if we don't use at least one of |
| 293 | * them here. |
| 294 | */ |
| 295 | { |
| 296 | static volatile uint8_t foo; |
| 297 | foo = setup->bRequest; |
| 298 | } |
| 299 | #endif |
| 300 | return 0; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | |
| 305 | static __bit my_descr(uint8_t type, uint8_t index, const uint8_t **reply, |
| 306 | uint8_t *size) __reentrant |
| 307 | { |
| 308 | index; /* suppress warning */ |
| 309 | if (type != DFU_DT_FUNCTIONAL) |
| 310 | return 0; |
| 311 | *reply = functional_descriptor; |
| 312 | *size = sizeof(functional_descriptor); |
| 313 | return 1; |
| 314 | } |
| 315 | |
| 316 | |
| 317 | static void my_reset(void) __reentrant |
| 318 | { |
| 319 | /* @@@ not nice -- think about where this should go */ |
| 320 | extern void run_payload(void); |
| 321 | |
| 322 | if (did_download) |
| 323 | run_payload(); |
| 324 | } |
| 325 | |
| 326 | |
| 327 | void dfu_init(void) |
| 328 | { |
| 329 | user_setup = my_setup; |
| 330 | user_get_descriptor = my_descr; |
| 331 | user_reset = my_reset; |
| 332 | } |