Add firmware for the ATUSB IEEE 802.15.4 USB Adapter
[linux-libre-firmware.git] / atusb / usb / dfu.c
diff --git a/atusb/usb/dfu.c b/atusb/usb/dfu.c
new file mode 100644 (file)
index 0000000..c84a28d
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * 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;
+}