1
0
mirror of git://projects.qi-hardware.com/ben-wpan.git synced 2024-12-24 15:33:20 +02:00
ben-wpan/atusb/fw/usb/dfu.c

261 lines
5.4 KiB
C
Raw Normal View History

/*
* 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;
}