X-Git-Url: https://jxself.org/git/?p=linux-libre-firmware.git;a=blobdiff_plain;f=atusb%2Fusb%2Fdfu.c;fp=atusb%2Fusb%2Fdfu.c;h=c84a28dfcef8847026432ab9f213a15d2fe7238d;hp=0000000000000000000000000000000000000000;hb=dd4bc9ff49b9a7075e579fdd62fd930d27a9a7df;hpb=c164bf7f87f9081fee7e1a186dd7a87a9a020b9e diff --git a/atusb/usb/dfu.c b/atusb/usb/dfu.c new file mode 100644 index 0000000..c84a28d --- /dev/null +++ b/atusb/usb/dfu.c @@ -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 +#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; +}