--- /dev/null
+/*
+ * 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 <stdbool.h>
+#include <stdint.h>
+
+#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;
+}