/* * boot/dfu.c - DFU protocol engine * * Written 2008-2011, 2013-2015 by Werner Almesberger * Copyright 2008-2011, 2013-2015 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ /* * http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf */ /* * A few, erm, shortcuts: * * - we don't bother with the app* states since DFU is all this firmware does * - after DFU_DNLOAD, we just block until things are written, so we never * enter dfuDNLOAD_SYNC or dfuDNBUSY * - no dfuMANIFEST_SYNC, dfuMANIFEST, or dfuMANIFEST_WAIT_RESET * - to keep our buffers small, we only accept EP0-sized blocks */ #include #include #include "usb.h" #include "dfu.h" #include "board.h" #ifndef NULL #define NULL 0 #endif #define debug(...) #define error(...) #ifndef DFU_ALT_SETTINGS #define DFU_ALT_SETTINGS 1 #endif #ifndef DFU_ALT_NAME_0_IDX #define DFU_ALT_NAME_0_IDX 0 #endif #ifndef DFU_ALT_NAME_1_IDX #define DFU_ALT_NAME_1_IDX 0 #endif #ifndef DFU_ALT_NAME_2_IDX #define DFU_ALT_NAME_2_IDX 0 #endif const uint8_t device_descriptor[] = { 18, /* bLength */ USB_DT_DEVICE, /* bDescriptorType */ LE(0x100), /* bcdUSB */ USB_CLASS_APP_SPEC, /* bDeviceClass */ 0x00, /* bDeviceSubClass (per interface) */ 0x00, /* bDeviceProtocol (per interface) */ EP0_SIZE, /* bMaxPacketSize */ LE(DFU_USB_VENDOR), /* idVendor */ LE(DFU_USB_PRODUCT), /* idProduct */ LE(0x0001), /* bcdDevice */ 0, /* iManufacturer */ 0, /* iProduct */ #ifdef HAS_BOARD_SERNUM 1, /* iSerialNumber */ #else 0, /* iSerialNumber */ #endif 1 /* bNumConfigurations */ }; const uint8_t config_descriptor[] = { 9, /* bLength */ USB_DT_CONFIG, /* bDescriptorType */ LE(9+9*DFU_ALT_SETTINGS), /* wTotalLength */ 1, /* bNumInterfaces */ 1, /* bConfigurationValue (> 0 !) */ 0, /* iConfiguration */ // USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED, USB_ATTR_BUS_POWERED, /* bmAttributes */ ((BOARD_MAX_mA)+1)/2, /* bMaxPower */ /* Interface #0 */ DFU_ITF_DESCR(0, 0, dfu_proto_dfu, DFU_ALT_NAME_0_IDX) #if DFU_ALT_SETTINGS > 1 DFU_ITF_DESCR(0, 1, dfu_proto_dfu, DFU_ALT_NAME_1_IDX) #endif #if DFU_ALT_SETTINGS > 2 DFU_ITF_DESCR(0, 2, dfu_proto_dfu, DFU_ALT_NAME_2_IDX) #endif }; static uint16_t next_block = 0; static bool did_download; static uint8_t buf[EP0_SIZE]; static void block_write(void *user) { uint16_t *size = user; dfu_flash_ops->write(buf, *size); } static bool block_receive(uint16_t length) { static uint16_t size; if (!dfu_flash_ops->can_write(length)) { dfu.state = dfuERROR; dfu.status = errADDRESS; return 0; } if (length > EP0_SIZE) { dfu.state = dfuERROR; dfu.status = errUNKNOWN; return 0; } size = length; usb_recv(&eps[0], buf, size, block_write, &size); return 1; } static bool block_transmit(uint16_t length) { uint16_t got; if (length > EP0_SIZE) { dfu.state = dfuERROR; dfu.status = errUNKNOWN; return 1; } got = dfu_flash_ops->read(buf, length); if (got < length) { length = got; dfu.state = dfuIDLE; } usb_send(&eps[0], buf, length, NULL, NULL); return 1; } static bool my_setup(const struct setup_request *setup) { bool ok; switch (setup->bmRequestType | setup->bRequest << 8) { case DFU_TO_DEV(DFU_DETACH): debug("DFU_DETACH\n"); /* * The DFU spec says thay this is sent in protocol 1 only. * However, dfu-util also sends it to get out of DFU mode, * so we just don't make a fuss and ignore it. */ return 1; case DFU_TO_DEV(DFU_DNLOAD): debug("DFU_DNLOAD\n"); if (dfu.state == dfuIDLE) { next_block = setup->wValue; dfu_flash_ops->start(); } else if (dfu.state != dfuDNLOAD_IDLE) { error("bad state\n"); return 0; } if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { debug("retransmisson\n"); return 1; } if (setup->wValue != next_block) { debug("bad block (%d vs. %d)\n", setup->wValue, next_block); dfu.state = dfuERROR; dfu.status = errUNKNOWN; return 1; } if (!setup->wLength) { debug("DONE\n"); dfu_flash_ops->end_write(); dfu.state = dfuIDLE; did_download = 1; return 1; } ok = block_receive(setup->wLength); next_block++; dfu.state = dfuDNLOAD_IDLE; return ok; case DFU_FROM_DEV(DFU_UPLOAD): debug("DFU_UPLOAD\n"); if (dfu.state == dfuIDLE) { next_block = setup->wValue; dfu_flash_ops->start(); } else if (dfu.state != dfuUPLOAD_IDLE) return 0; if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { debug("retransmisson\n"); /* @@@ try harder */ dfu.state = dfuERROR; dfu.status = errUNKNOWN; return 1; } if (setup->wValue != next_block) { debug("bad block (%d vs. %d)\n", setup->wValue, next_block); dfu.state = dfuERROR; dfu.status = errUNKNOWN; return 1; } ok = block_transmit(setup->wLength); next_block++; dfu.state = dfuUPLOAD_IDLE; return ok; case DFU_TO_DEV(DFU_ABORT): debug("DFU_ABORT\n"); dfu.state = dfuIDLE; dfu.status = OK; return 1; default: return dfu_setup_common(setup); } } static void my_reset(void) { #if 0 /* @@@ not nice -- think about where this should go */ extern void run_payload(void); if (did_download) run_payload(); #endif } void dfu_init(void) { user_setup = my_setup; user_get_descriptor = dfu_my_descr; user_reset = my_reset; }