[U-Boot] [PATCH 1/1] usb/gadget: Add Device Firmware Upgrade (DFU) support.
Stefan Schmidt
stefan at datenfreihafen.org
Thu Nov 3 16:56:36 CET 2011
A vendor independent standard from the USB forum to upgrade firmware on
devices over USB. U-Boot is seen as the device to be upgraded here and
exposes the functionality over the USB gadget framework.
Signed-off-by: Stefan Schmidt <stefan at datenfreihafen.org>
---
drivers/serial/usbtty.c | 22 +-
drivers/serial/usbtty.h | 6 +
drivers/usb/gadget/Makefile | 1 +
drivers/usb/gadget/core.c | 19 +
drivers/usb/gadget/dfu.c | 1007 +++++++++++++++++++++++++++++++++++++++++
drivers/usb/gadget/ep0.c | 50 ++-
include/usb_dfu.h | 95 ++++
include/usb_dfu_descriptors.h | 94 ++++
include/usbdevice.h | 13 +
9 files changed, 1303 insertions(+), 4 deletions(-)
create mode 100644 drivers/usb/gadget/dfu.c
create mode 100644 include/usb_dfu.h
create mode 100644 include/usb_dfu_descriptors.h
diff --git a/drivers/serial/usbtty.c b/drivers/serial/usbtty.c
index cffd5a2..b13b497 100644
--- a/drivers/serial/usbtty.c
+++ b/drivers/serial/usbtty.c
@@ -22,6 +22,8 @@
*/
#include <common.h>
+#include <usb_dfu_descriptors.h>
+#include <usb_dfu.h>
#include <config.h>
#include <circbuf.h>
#include <stdio_dev.h>
@@ -101,7 +103,7 @@ extern struct usb_string_descriptor **usb_strings;
static unsigned short rx_endpoint = 0;
static unsigned short tx_endpoint = 0;
static unsigned short interface_count = 0;
-static struct usb_string_descriptor *usbtty_string_table[STR_COUNT];
+static struct usb_string_descriptor *usbtty_string_table[NUM_STRINGS];
/* USB Descriptor Strings */
static u8 wstrLang[4] = {4,USB_DT_STRING,0x9,0x4};
@@ -151,6 +153,10 @@ struct acm_config_desc {
/* Slave Interface */
struct usb_interface_descriptor data_class_interface;
struct usb_endpoint_descriptor data_endpoints[NUM_ENDPOINTS-1];
+#ifdef CONFIG_USBD_DFU
+ struct usb_interface_descriptor uif_dfu;
+ struct usb_dfu_func_descriptor func_dfu;
+#endif
} __attribute__((packed));
static struct acm_config_desc acm_configuration_descriptors[NUM_CONFIGS] = {
@@ -161,7 +167,11 @@ static struct acm_config_desc acm_configuration_descriptors[NUM_CONFIGS] = {
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength =
cpu_to_le16(sizeof(struct acm_config_desc)),
+#ifdef CONFIG_USBD_DFU
+ .bNumInterfaces = NUM_ACM_INTERFACES + 1,
+#else
.bNumInterfaces = NUM_ACM_INTERFACES,
+#endif
.bConfigurationValue = 1,
.iConfiguration = STR_CONFIG,
.bmAttributes =
@@ -260,6 +270,11 @@ static struct acm_config_desc acm_configuration_descriptors[NUM_CONFIGS] = {
.bInterval = 0xFF,
},
},
+#ifdef CONFIG_USBD_DFU
+ /* Interface 3 */
+ .uif_dfu = DFU_RT_IF_DESC,
+ .func_dfu = DFU_FUNC_DESC,
+#endif
},
};
@@ -370,7 +385,7 @@ static int fill_buffer (circbuf_t * buf);
void usbtty_poll (void);
/* utility function for converting char* to wide string used by USB */
-static void str2wide (char *str, u16 * wide)
+void str2wide (char *str, u16 * wide)
{
int i;
for (i = 0; i < strlen (str) && str[i]; i++){
@@ -639,6 +654,9 @@ static void usbtty_init_instances (void)
device_instance->bus = bus_instance;
device_instance->configurations = NUM_CONFIGS;
device_instance->configuration_instance_array = config_instance;
+#ifdef CONFIG_USBD_DFU
+ dfu_init_instance(device_instance);
+#endif
/* initialize bus instance */
memset (bus_instance, 0, sizeof (struct usb_bus_instance));
diff --git a/drivers/serial/usbtty.h b/drivers/serial/usbtty.h
index a23169a..bb45025 100644
--- a/drivers/serial/usbtty.h
+++ b/drivers/serial/usbtty.h
@@ -84,4 +84,10 @@
#define STR_CTRL_INTERFACE 0x06
#define STR_COUNT 0x07
+#ifdef CONFIG_USBD_DFU
+#define NUM_STRINGS DFU_STR_COUNT
+#else
+#define NUM_STRINGS STR_COUNT
+#endif
+
#endif
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 7d5b504..570470d 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -34,6 +34,7 @@ else
ifdef CONFIG_USB_DEVICE
COBJS-y += core.o
COBJS-y += ep0.o
+COBJS-y += dfu.o
COBJS-$(CONFIG_OMAP1510) += omap1510_udc.o
COBJS-$(CONFIG_OMAP1610) += omap1510_udc.o
COBJS-$(CONFIG_MPC885_FAMILY) += mpc8xx_udc.o
diff --git a/drivers/usb/gadget/core.c b/drivers/usb/gadget/core.c
index 67b6681..e5ee9f6 100644
--- a/drivers/usb/gadget/core.c
+++ b/drivers/usb/gadget/core.c
@@ -31,6 +31,7 @@
#include <malloc.h>
#include <usbdevice.h>
+#include <usb_dfu.h>
#define MAX_INTERFACES 2
@@ -209,6 +210,10 @@ struct usb_alternate_instance *usbd_device_alternate_instance (struct usb_device
*/
struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port)
{
+#ifdef CONFIG_USBD_DFU
+ if (device->dfu_state != DFU_STATE_appIDLE)
+ return device->dfu_dev_desc;
+#endif
return (device->device_descriptor);
}
@@ -229,6 +234,10 @@ struct usb_configuration_descriptor *usbd_device_configuration_descriptor (struc
if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) {
return NULL;
}
+#ifdef CONFIG_USBD_DFU
+ if (device->dfu_state != DFU_STATE_appIDLE)
+ return (&device->dfu_cfg_desc->ucfg);
+#endif
return (configuration_instance->configuration_descriptor);
}
@@ -250,6 +259,13 @@ struct usb_interface_descriptor *usbd_device_interface_descriptor (struct usb_de
if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) {
return NULL;
}
+#ifdef CONFIG_USBD_DFU
+ if (device->dfu_state != DFU_STATE_appIDLE) {
+ if (alternate < 0 || alternate >= DFU_NUM_ALTERNATES)
+ return NULL;
+ return &device->dfu_cfg_desc->uif[alternate];
+ }
+#endif
if ((alternate < 0) || (alternate >= interface_instance->alternates)) {
return NULL;
}
@@ -680,4 +696,7 @@ void usbd_device_event_irq (struct usb_device_instance *device, usb_device_event
/* usbdbg("calling device->event"); */
device->event(device, event, data);
}
+#ifdef CONFIG_USBD_DFU
+ dfu_event(device, event, data);
+#endif
}
diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c
new file mode 100644
index 0000000..64083d6
--- /dev/null
+++ b/drivers/usb/gadget/dfu.c
@@ -0,0 +1,1007 @@
+/*
+ * (C) 2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge at openmoko.org>
+ *
+ * based on existing SAM7DFU code from OpenPCD:
+ * (C) Copyright 2006 by Harald Welte <hwelte at hmw-consulting.de>
+ *
+ * FIXME Add copyright for Bernard, add my copyright + LF
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <config.h>
+#if defined(CONFIG_USBD_DFU)
+
+/* FIXME disbale debug */
+#define DEBUG
+
+#include <common.h>
+DECLARE_GLOBAL_DATA_PTR; /* FIXME needed? */
+
+#include <malloc.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <asm/errno.h>
+#include <usbdevice.h>
+#include <usb_dfu.h>
+#include <usb_dfu_descriptors.h>
+
+#include <nand.h>
+#include <jffs2/load_kernel.h>
+int mtdparts_init(void);
+extern struct list_head devices;
+
+#include "../../serial/usbtty.h" /* for STR_* defs */
+
+#define RET_NOTHING 0
+#define RET_ZLP 1
+#define RET_STALL 2
+
+#define POLL_TIMEOUT_MILLISECONDS 5
+
+volatile enum dfu_state *system_dfu_state; /* for 3rd parties */
+
+
+struct dnload_state {
+ nand_info_t *nand;
+ struct part_info *part;
+ unsigned int part_net_size; /* net size (no bad blocks) of part */
+
+ nand_erase_options_t erase_opts;
+
+ unsigned char *ptr; /* pointer to next empty byte in buffer */
+ unsigned int off; /* offset of current erase page in flash chip */
+ unsigned char *buf; /* pointer to allocated erase page buffer */
+
+ /* unless doing an atomic transfer, we use the static buffer below.
+ * This saves us from having to clean up dynamic allications in the
+ * various error paths of the code. Also, it will always work, no
+ * matter what the memory situation is. */
+ unsigned char _buf[0x20000]; /* FIXME: depends flash page size */
+};
+
+static struct dnload_state _dnstate;
+
+/* FIXME: Move this to the nand subsystem */
+/* Return the 'net size' of the partition (i.e. excluding any bad blocks) */
+unsigned int nand_net_part_size(struct part_info *part)
+{
+ struct mtd_info *mtd;
+ unsigned int offs;
+ unsigned int bb_delta = 0;
+
+ if (!part || !part->dev || !part->dev->id ||
+ part->dev->id->num >= CONFIG_SYS_MAX_NAND_DEVICE)
+ return 0;
+
+ mtd = &nand_info[part->dev->id->num];
+
+ for (offs = part->offset; offs < part->offset + part->size;
+ offs += mtd->erasesize) {
+ if (nand_isbad_bbt(mtd, offs, 0))
+ bb_delta += mtd->erasesize;
+ }
+
+ return part->size - bb_delta;
+}
+
+static struct part_info *get_partition_nand(int idx)
+{
+ struct mtd_device *dev;
+ struct part_info *part;
+ struct list_head *pentry;
+ int i;
+
+ if (mtdparts_init())
+ return NULL;
+ if (list_empty(&devices))
+ return NULL;
+
+ dev = list_entry(devices.next, struct mtd_device, link);
+ i = 0;
+ list_for_each(pentry, &dev->parts) {
+ if (i == idx) {
+ part = list_entry(pentry, struct part_info, link);
+ return part;
+ }
+ i++;
+ }
+
+ return NULL;
+}
+
+static int initialize_ds_nand(struct usb_device_instance *dev,
+ struct dnload_state *ds)
+{
+ ds->part = get_partition_nand(dev->alternate - 1);
+ if (!ds->part) {
+ printf("DFU: unable to find partition %u\b", dev->alternate-1);
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+ ds->nand = &nand_info[ds->part->dev->id->num];
+ ds->off = ds->part->offset;
+ ds->part_net_size = nand_net_part_size(ds->part);
+
+ if (ds->nand->erasesize > sizeof(ds->_buf)) {
+ printf("Warning - NAND ERASESIZE bigger than static buffer\n");
+ ds->buf = malloc(ds->nand->erasesize);
+ if (!ds->buf) {
+ printf("DFU: can't allocate %u bytes\n",
+ ds->nand->erasesize);
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+ } else
+ ds->buf = ds->_buf;
+
+ ds->ptr = ds->buf;
+
+ memset(&ds->erase_opts, 0, sizeof(ds->erase_opts));
+ ds->erase_opts.quiet = 1;
+ /* FIXME: do this more dynamic */
+ if ((!strcmp(ds->part->name, "rootfs")) ||
+ (!strcmp(ds->part->name, "fs")))
+ ds->erase_opts.jffs2 = 1;
+
+ /* FIXME: How to set these options without write_opts?
+ * ds->write_opts.pad = 1;
+ * ds->write_opts.blockalign = 1;
+ * ds->write_opts.quiet = 1;
+ */
+
+ debug("initialize_ds_nand(dev=%p, ds=%p): ", dev, ds);
+ debug("nand=%p, ptr=%p, buf=%p, off=0x%x\n", ds->nand, ds->ptr,
+ ds->buf, ds->off);
+
+ return RET_NOTHING;
+}
+
+static int erase_flash_verify_nand(struct urb *urb, struct dnload_state *ds,
+ unsigned long erasesize, size_t size)
+{
+ struct usb_device_instance *dev = urb->device;
+ int rc;
+
+ debug("erase_flash_verify_nand(urb=%p, ds=%p, erase=0x%lx size=0x%x)\n",
+ urb, ds, erasesize, size);
+
+ if (erasesize == ds->nand->erasesize) {
+ /* we're only writing a single block and need to
+ * do bad block skipping / offset adjustments our own */
+ while (ds->nand->block_isbad(ds->nand, ds->off)) {
+ debug("SKIP_ONE_BLOCK(0x%08x)!!\n", ds->off);
+ ds->off += ds->nand->erasesize;
+ }
+ }
+
+ /* we have finished one eraseblock, flash it */
+ ds->erase_opts.offset = ds->off;
+ ds->erase_opts.length = erasesize;
+ debug("Erasing 0x%x bytes @ offset 0x%x (jffs=%u)\n",
+ (unsigned int)ds->erase_opts.length,
+ (unsigned int)ds->erase_opts.offset,
+ ds->erase_opts.jffs2);
+ rc = nand_erase_opts(ds->nand, &ds->erase_opts);
+ if (rc) {
+ debug("Error erasing\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errERASE;
+ return RET_STALL;
+ }
+
+ debug("Writing 0x%x bytes @ offset 0x%x\n", size, ds->off);
+ /* FIXME handle oob */
+ rc = nand_write_skip_bad(ds->nand, ds->off, &size, ds->buf, 0);
+ if (rc) {
+ debug("Error writing\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errWRITE;
+ return RET_STALL;
+ }
+
+ ds->off += size;
+ ds->ptr = ds->buf;
+
+ /* FIXME: implement verify! */
+ return RET_NOTHING;
+}
+
+static int erase_tail_clean_nand(struct urb *urb, struct dnload_state *ds)
+{
+ struct usb_device_instance *dev = urb->device;
+ int rc;
+
+ ds->erase_opts.offset = ds->off;
+ ds->erase_opts.length = ds->part->size - (ds->off - ds->part->offset);
+ debug("Erasing tail of 0x%x bytes @ offset 0x%x (jffs=%u)\n",
+ (unsigned int)ds->erase_opts.length,
+ (unsigned int)ds->erase_opts.offset,
+ ds->erase_opts.jffs2);
+ rc = nand_erase_opts(ds->nand, &ds->erase_opts);
+ if (rc) {
+ printf("Error erasing tail\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errERASE;
+ return RET_STALL;
+ }
+
+ ds->off += ds->erase_opts.length; /* for consistency */
+
+ return RET_NOTHING;
+}
+
+/* Read the next erase block from NAND into buffer */
+static int read_next_nand(struct urb *urb, struct dnload_state *ds, size_t len)
+{
+ struct usb_device_instance *dev = urb->device;
+ int rc;
+
+ debug("Reading 0x%x at 0x%x to 0x%p\n", len, ds->off, ds->buf);
+ rc = nand_read_skip_bad(ds->nand, ds->off, &len, ds->buf);
+ if (rc) {
+ debug("Error reading\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errWRITE;
+ return RET_STALL;
+ }
+ ds->off += len;
+ ds->ptr = ds->buf;
+
+ return RET_NOTHING;
+}
+
+static int get_dfu_loadaddr(uint8_t **loadaddr)
+{
+ const char *s;
+ s = getenv("loadaddr");
+ if (s != NULL) {
+ *loadaddr = (uint8_t *)simple_strtoul(s, NULL, 16);
+ return 1;
+ } else
+ return 0;
+}
+
+static int get_dfu_filesize(unsigned long *filesize)
+{
+ const char *s;
+ s = getenv("filesize");
+ if (s != NULL) {
+ *filesize = simple_strtoul(s, NULL, 16);
+ return 1;
+ } else
+ return 0;
+}
+
+static int handle_dnload(struct urb *urb, u_int16_t val, u_int16_t len,
+ int first)
+{
+ struct usb_device_instance *dev = urb->device;
+ struct dnload_state *ds = &_dnstate;
+ unsigned int actual_len = len;
+ unsigned int remain_len;
+ unsigned long size;
+ uint8_t *loadaddr;
+ int rc;
+
+ debug("download(len=%u, first=%u) ", len, first);
+
+ if (!get_dfu_loadaddr(&loadaddr)) {
+ printf("Error: DFU Download requires loadaddr to be set.\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+
+ if (len > CONFIG_USBD_DFU_XFER_SIZE) {
+ /* Too big. Not that we'd really care, but it's a
+ * DFU protocol violation */
+ debug("length exceeds flash page size ");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+
+ if (first && dev->alternate != 0) {
+ /* Make sure that we have a valid mtd partition table */
+ char *mtdp = getenv("mtdparts");
+ if (!mtdp)
+ run_command("dynpart", 0);
+ }
+
+ if (len == 0) {
+ debug("zero-size write -> MANIFEST_SYNC ");
+ dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
+
+ /* cleanup */
+ switch (dev->alternate) {
+ char buf[12];
+ case 0:
+ sprintf(buf, "%x", ds->ptr - ds->buf);
+ setenv("filesize", buf);
+ ds->ptr = ds->buf;
+ break;
+ default:
+ rc = 0;
+ if (ds->ptr > ds->buf)
+ rc = erase_flash_verify_nand(urb, ds,
+ ds->nand->erasesize,
+ ds->nand->erasesize);
+ /* rootfs partition */
+ if (!rc && ((!strcmp(ds->part->name, "rootfs"))
+ || (!strcmp(ds->part->name, "fs"))))
+ rc = erase_tail_clean_nand(urb, ds);
+
+ ds->nand = NULL;
+ break;
+ }
+
+ return RET_ZLP;
+ }
+
+ if (urb->actual_length != len) {
+ debug("urb->actual_length(%u) != len(%u) ?!? ",
+ urb->actual_length, len);
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+
+ if (first && ds->buf && ds->buf != ds->_buf && ds->buf != loadaddr) {
+ free(ds->buf);
+ ds->buf = ds->_buf;
+ }
+
+ switch (dev->alternate) {
+ case 0:
+ if (first) {
+ printf("Starting DFU DOWNLOAD to RAM (0x%p)\n",
+ loadaddr);
+ ds->buf = loadaddr;
+ ds->ptr = ds->buf;
+ }
+
+ memcpy(ds->ptr, urb->buffer, len);
+ ds->ptr += len;
+ break;
+ case 1:
+ if (first) {
+ rc = initialize_ds_nand(dev, ds);
+ if (rc)
+ return rc;
+ ds->buf = malloc(ds->part_net_size);
+ if (!ds->buf) {
+ printf("No memory for atomic buffer!!\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errUNKNOWN;
+ return RET_STALL;
+ }
+ ds->ptr = ds->buf;
+ printf("Starting Atomic DFU DOWNLOAD to partition %s\n",
+ ds->part->name);
+ }
+
+ remain_len = (ds->buf + ds->part_net_size) - ds->ptr;
+ if (remain_len < len) {
+ len = remain_len;
+ printf("End of write exceeds partition end\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+ memcpy(ds->ptr, urb->buffer, len);
+ ds->ptr += len;
+ break;
+ default:
+ if (first) {
+ rc = initialize_ds_nand(dev, ds);
+ if (rc)
+ return rc;
+ printf("Starting DFU DOWNLOAD to partition '%s'\n",
+ ds->part->name);
+ }
+
+ size = ds->nand->erasesize;
+ remain_len = ds->buf + size - ds->ptr;
+ if (remain_len < len)
+ actual_len = remain_len;
+
+ memcpy(ds->ptr, urb->buffer, actual_len);
+ ds->ptr += actual_len;
+
+ /* check partition end */
+ if (ds->off + (ds->ptr - ds->buf) > ds->part->offset +
+ ds->part->size) {
+ printf("End of write exceeds partition end\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return RET_STALL;
+ }
+
+ if (ds->ptr >= ds->buf + size) {
+ rc = erase_flash_verify_nand(urb, ds,
+ ds->nand->erasesize,
+ ds->nand->erasesize);
+ if (rc)
+ return rc;
+ /* copy remainder of data into buffer */
+ memcpy(ds->ptr, urb->buffer + actual_len, len -
+ actual_len);
+ ds->ptr += (len - actual_len);
+ }
+ break;
+ }
+
+ return RET_ZLP;
+}
+
+static int handle_upload(struct urb *urb, u_int16_t val, u_int16_t len,
+ int first)
+{
+ struct usb_device_instance *dev = urb->device;
+ struct dnload_state *ds = &_dnstate;
+ unsigned int remain;
+ uint8_t *loadaddr;
+ unsigned long filesize;
+ int rc;
+
+ debug("upload(val=0x%02x, len=%u, first=%u) ", val, len, first);
+
+ if (!get_dfu_loadaddr(&loadaddr) || !get_dfu_filesize(&filesize)) {
+ printf("Error: DFU Upload requires loadaddr and filesize to be "
+ "set.\n");
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ return -EINVAL;
+ }
+
+ if (len > CONFIG_USBD_DFU_XFER_SIZE) {
+ /* Too big */
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ dev->dfu_status = DFU_STATUS_errADDRESS;
+ debug("Error: Transfer size > CONFIG_USBD_DFU_XFER_SIZE ");
+ return -EINVAL;
+ }
+
+ switch (dev->alternate) {
+ case 0:
+ if (first) {
+ printf("Starting DFU Upload of RAM (0x%p)\n", loadaddr);
+ ds->buf = loadaddr;
+ ds->ptr = ds->buf;
+ }
+
+ if (ds->ptr + len > loadaddr + filesize)
+ len = (loadaddr + filesize) - ds->ptr;
+
+ memcpy(urb->buffer, ds->ptr, len);
+ urb->actual_length = len;
+ ds->ptr += len;
+ break;
+ default:
+ if (first) {
+ rc = initialize_ds_nand(dev, ds);
+ if (rc)
+ return -EINVAL;
+ printf("Starting DFU Upload of partition '%s'\n",
+ ds->part->name);
+ }
+
+ if (len > ds->nand->erasesize) {
+ printf("We don't support transfers bigger than %u\n",
+ ds->nand->erasesize);
+ len = ds->nand->erasesize;
+ }
+
+ /* limit length to whatever number of bytes is left in
+ * this partition */
+ remain = (ds->part->offset + ds->part->size) - ds->off;
+ if (len > remain)
+ len = remain;
+
+ rc = read_next_nand(urb, ds, len);
+ if (rc)
+ return -EINVAL;
+
+ debug("uploading %u bytes ", len);
+ urb->buffer = ds->buf;
+ urb->actual_length = len;
+ break;
+ }
+
+ debug("returning len=%u\n", len);
+ return len;
+}
+
+static void handle_getstatus(struct urb *urb, int max)
+{
+ struct usb_device_instance *dev = urb->device;
+ struct dfu_status *dstat = (struct dfu_status *) urb->buffer;
+
+ debug("getstatus ");
+
+ if (!urb->buffer || urb->buffer_length < sizeof(*dstat)) {
+ debug("invalid urb! ");
+ return;
+ }
+
+ switch (dev->dfu_state) {
+ case DFU_STATE_dfuDNLOAD_SYNC:
+ case DFU_STATE_dfuDNBUSY:
+ debug("DNLOAD_IDLE ");
+ dev->dfu_state = DFU_STATE_dfuDNLOAD_IDLE;
+#if 0
+ debug("DNBUSY ");
+ dev->dfu_state = DFU_STATE_dfuDNBUSY;
+#endif
+ break;
+ case DFU_STATE_dfuMANIFEST_SYNC:
+ break;
+ default:
+ /* return; */
+ break;
+ }
+
+ /* send status response */
+ dstat->bStatus = dev->dfu_status;
+ dstat->bState = dev->dfu_state;
+ dstat->iString = 0;
+ /* FIXME: Use real values from flash subsystem here instead a hardcoded
+ * value */
+ dstat->bwPollTimeout[0] = POLL_TIMEOUT_MILLISECONDS & 0xff;
+ dstat->bwPollTimeout[1] = (POLL_TIMEOUT_MILLISECONDS >> 8) & 0xff;
+ dstat->bwPollTimeout[2] = (POLL_TIMEOUT_MILLISECONDS >> 16) & 0xff;
+ urb->actual_length = MIN(sizeof(*dstat), max);
+
+ /* we don't need to explicitly send data here, will
+ * be done by the original caller! */
+}
+
+static void handle_getstate(struct urb *urb, int max)
+{
+ debug("getstate ");
+
+ if (!urb->buffer || urb->buffer_length < sizeof(u_int8_t)) {
+ debug("invalid urb! ");
+ return;
+ }
+
+ urb->buffer[0] = urb->device->dfu_state & 0xff;
+ urb->actual_length = sizeof(u_int8_t);
+}
+
+#ifndef CONFIG_USBD_PRODUCTID_DFU
+#define CONFIG_USBD_PRODUCTID_DFU CONFIG_USBD_PRODUCTID_CDCACM
+#endif
+
+static const struct usb_device_descriptor dfu_dev_descriptor = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x0100,
+ .bDeviceClass = 0x00,
+ .bDeviceSubClass = 0x00,
+ .bDeviceProtocol = 0x00,
+ .bMaxPacketSize0 = EP0_MAX_PACKET_SIZE,
+ .idVendor = CONFIG_USBD_VENDORID,
+ .idProduct = CONFIG_USBD_PRODUCTID_DFU,
+ .bcdDevice = 0x0000,
+ .iManufacturer = DFU_STR_MANUFACTURER,
+ .iProduct = DFU_STR_PRODUCT,
+ .iSerialNumber = DFU_STR_SERIAL,
+ .bNumConfigurations = 0x01,
+};
+
+static struct _dfu_desc dfu_cfg_descriptor = {
+ .ucfg = {
+ .bLength = USB_DT_CONFIG_SIZE,
+ .bDescriptorType = USB_DT_CONFIG,
+ .wTotalLength = USB_DT_CONFIG_SIZE +
+ DFU_NUM_ALTERNATES *
+ USB_DT_INTERFACE_SIZE +
+ USB_DT_DFU_SIZE,
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = DFU_STR_CONFIG,
+ .bmAttributes = BMATTRIBUTE_RESERVED,
+ .bMaxPower = 50,
+ },
+ .func_dfu = DFU_FUNC_DESC,
+};
+
+int dfu_ep0_handler(struct urb *urb)
+{
+ int rc, ret = RET_NOTHING;
+ u_int8_t req = urb->device_request.bRequest;
+ u_int16_t val = urb->device_request.wValue;
+ u_int16_t len = urb->device_request.wLength;
+ struct usb_device_instance *dev = urb->device;
+
+ debug("dfu_ep0(req=0x%x, val=0x%x, len=%u) old_state = %u ",
+ req, val, len, dev->dfu_state);
+
+ switch (dev->dfu_state) {
+ case DFU_STATE_appIDLE:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ case USB_REQ_DFU_DETACH:
+ dev->dfu_state = DFU_STATE_appDETACH;
+ ret = RET_ZLP;
+ goto out;
+ break;
+ default:
+ ret = RET_STALL;
+ }
+ break;
+ case DFU_STATE_appDETACH:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_appIDLE;
+ ret = RET_STALL;
+ goto out;
+ break;
+ }
+ /* FIXME: implement timer to return to appIDLE */
+ break;
+ case DFU_STATE_dfuIDLE:
+ switch (req) {
+ case USB_REQ_DFU_DNLOAD:
+ printf("Got DNLOAD req in dfuIDLE, len = %d\n", len);
+ if (len == 0) {
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ goto out;
+ }
+ dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+ ret = handle_dnload(urb, val, len, 1);
+ break;
+ case USB_REQ_DFU_UPLOAD:
+ dev->dfu_state = DFU_STATE_dfuUPLOAD_IDLE;
+ handle_upload(urb, val, len, 1);
+ break;
+ case USB_REQ_DFU_ABORT:
+ /* no zlp? */
+ ret = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ case USB_REQ_DFU_DETACH:
+ /* Proprietary extension: 'detach' from idle mode and
+ * get back to runtime mode in case of USB Reset. As
+ * much as I dislike this, we just can't use every USB
+ * bus reset to switch back to runtime mode, since at
+ * least the Linux USB stack likes to send a number of
+ * resets in a row :( */
+ dev->dfu_state = DFU_STATE_dfuMANIFEST_WAIT_RST;
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ goto out;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuDNLOAD_SYNC:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ /* FIXME: state transition depending on block
+ * completeness */
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ goto out;
+ }
+ break;
+ case DFU_STATE_dfuDNBUSY:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ /* FIXME: only accept getstatus if bwPollTimeout
+ * has elapsed */
+ handle_getstatus(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ goto out;
+ }
+ break;
+ case DFU_STATE_dfuDNLOAD_IDLE:
+ switch (req) {
+ case USB_REQ_DFU_DNLOAD:
+ dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+ ret = handle_dnload(urb, val, len, 0);
+ break;
+ case USB_REQ_DFU_ABORT:
+ dev->dfu_state = DFU_STATE_dfuIDLE;
+ ret = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuMANIFEST_SYNC:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ /* We're MainfestationTolerant */
+ dev->dfu_state = DFU_STATE_dfuIDLE;
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuMANIFEST:
+ /* we should never go here */
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ break;
+ case DFU_STATE_dfuMANIFEST_WAIT_RST:
+ /* we should never go here */
+ break;
+ case DFU_STATE_dfuUPLOAD_IDLE:
+ switch (req) {
+ case USB_REQ_DFU_UPLOAD:
+ /* state transition if less data then requested */
+ rc = handle_upload(urb, val, len, 0);
+ if (rc >= 0 && rc < len)
+ dev->dfu_state = DFU_STATE_dfuIDLE;
+ break;
+ case USB_REQ_DFU_ABORT:
+ dev->dfu_state = DFU_STATE_dfuIDLE;
+ /* no zlp? */
+ ret = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuERROR:
+ switch (req) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(urb, len);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(urb, len);
+ break;
+ case USB_REQ_DFU_CLRSTATUS:
+ dev->dfu_state = DFU_STATE_dfuIDLE;
+ dev->dfu_status = DFU_STATUS_OK;
+ /* no zlp? */
+ ret = RET_ZLP;
+ break;
+ default:
+ dev->dfu_state = DFU_STATE_dfuERROR;
+ ret = RET_STALL;
+ break;
+ }
+ break;
+ default:
+ return DFU_EP0_UNHANDLED;
+ break;
+ }
+
+out:
+ debug("new_state = %u, ret = %u\n", dev->dfu_state, ret);
+
+ switch (ret) {
+ case RET_ZLP:
+ urb->actual_length = 0;
+ return DFU_EP0_ZLP;
+ break;
+ case RET_STALL:
+ return DFU_EP0_STALL;
+ break;
+ case RET_NOTHING:
+ break;
+ }
+
+ return DFU_EP0_DATA;
+}
+
+void str2wide(char *str, u16 * wide);
+static struct usb_string_descriptor *create_usbstring(char *string)
+{
+ struct usb_string_descriptor *strdesc;
+ int size = sizeof(*strdesc) + strlen(string)*2;
+
+ if (size > 255)
+ return NULL;
+
+ strdesc = malloc(size);
+ if (!strdesc)
+ return NULL;
+
+ strdesc->bLength = size;
+ strdesc->bDescriptorType = USB_DT_STRING;
+ str2wide(string, strdesc->wData);
+
+ return strdesc;
+}
+
+
+static void dfu_init_strings(struct usb_device_instance *dev)
+{
+ int i;
+ struct usb_string_descriptor *strdesc;
+
+ strdesc = create_usbstring(CONFIG_DFU_CFG_STR);
+ usb_strings[DFU_STR_CONFIG] = strdesc;
+
+ for (i = 0; i < DFU_NUM_ALTERNATES; i++) {
+ if (i == 0) {
+ strdesc = create_usbstring(CONFIG_DFU_ALT0_STR);
+ } else {
+ struct part_info *part = get_partition_nand(i-1);
+
+ if (part)
+ strdesc = create_usbstring(part->name);
+ else
+ strdesc =
+ create_usbstring("undefined partition");
+ }
+ if (!strdesc)
+ continue;
+ usb_strings[STR_COUNT+i+1] = strdesc;
+ }
+}
+
+int dfu_init_instance(struct usb_device_instance *dev)
+{
+ int i;
+
+ for (i = 0; i != DFU_NUM_ALTERNATES; i++) {
+ struct usb_interface_descriptor *uif =
+ dfu_cfg_descriptor.uif+i;
+
+ uif->bLength = USB_DT_INTERFACE_SIZE;
+ uif->bDescriptorType = USB_DT_INTERFACE;
+ uif->bAlternateSetting = i;
+ uif->bInterfaceClass = 0xfe;
+ uif->bInterfaceSubClass = 1;
+ uif->bInterfaceProtocol = 2; /* FIXME: Corect number? */
+ uif->iInterface = DFU_STR_ALT(i);
+ }
+
+ dev->dfu_dev_desc = &dfu_dev_descriptor;
+ dev->dfu_cfg_desc = &dfu_cfg_descriptor;
+ dev->dfu_state = DFU_STATE_appIDLE;
+ dev->dfu_status = DFU_STATUS_OK;
+
+ if (system_dfu_state)
+ printf("SURPRISE: system_dfu_state is already set\n");
+ system_dfu_state = &dev->dfu_state;
+
+ dfu_init_strings(dev);
+
+ return 0;
+}
+
+/* event handler for usb device state events */
+void dfu_event(struct usb_device_instance *device,
+ usb_device_event_t event, int data)
+{
+ char *out;
+ static int stdout_switched;
+
+ switch (event) {
+ case DEVICE_RESET:
+ switch (device->dfu_state) {
+ case DFU_STATE_appDETACH:
+ device->dfu_state = DFU_STATE_dfuIDLE;
+ out = getenv("stdout");
+ if (out && !strcmp(out, "usbtty")) {
+ setenv("stdout", "vga");
+ setenv("stderr", "vga");
+ stdout_switched = 1;
+ }
+ printf("DFU: Switching to DFU Mode\n");
+ break;
+ case DFU_STATE_dfuMANIFEST_WAIT_RST:
+ device->dfu_state = DFU_STATE_appIDLE;
+ printf("DFU: Switching back to Runtime mode\n");
+ if (stdout_switched) {
+ setenv("stdout", "usbtty");
+ setenv("stderr", "usbtty");
+ stdout_switched = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case DEVICE_CONFIGURED:
+ case DEVICE_DE_CONFIGURED:
+ debug("SET_CONFIGURATION(%u) ", device->configuration);
+ /* fallthrough */
+ case DEVICE_SET_INTERFACE:
+ debug("SET_INTERFACE(%u,%u) old_state = %u ",
+ device->interface, device->alternate,
+ device->dfu_state);
+ switch (device->dfu_state) {
+ case DFU_STATE_appIDLE:
+ case DFU_STATE_appDETACH:
+ case DFU_STATE_dfuIDLE:
+ case DFU_STATE_dfuMANIFEST_WAIT_RST:
+ /* do nothing, we're fine */
+ break;
+ case DFU_STATE_dfuDNLOAD_SYNC:
+ case DFU_STATE_dfuDNBUSY:
+ case DFU_STATE_dfuDNLOAD_IDLE:
+ case DFU_STATE_dfuMANIFEST:
+ device->dfu_state = DFU_STATE_dfuERROR;
+ device->dfu_status = DFU_STATUS_errNOTDONE;
+ /* FIXME: free malloc()ed buffer! */
+ break;
+ case DFU_STATE_dfuMANIFEST_SYNC:
+ case DFU_STATE_dfuUPLOAD_IDLE:
+ case DFU_STATE_dfuERROR:
+ device->dfu_state = DFU_STATE_dfuERROR;
+ device->dfu_status = DFU_STATUS_errUNKNOWN;
+ break;
+ }
+ debug("new_state = %u\n", device->dfu_state);
+ break;
+ default:
+ break;
+ }
+}
+#endif /* CONFIG_USBD_DFU */
diff --git a/drivers/usb/gadget/ep0.c b/drivers/usb/gadget/ep0.c
index 2b4ec44..ddeed93 100644
--- a/drivers/usb/gadget/ep0.c
+++ b/drivers/usb/gadget/ep0.c
@@ -52,6 +52,11 @@
#include <common.h>
#include <usbdevice.h>
+DECLARE_GLOBAL_DATA_PTR;
+
+#ifdef CONFIG_USBD_DFU
+#include <usb_dfu.h>
+#endif
#if 0
#define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args)
@@ -198,7 +203,7 @@ static int ep0_get_descriptor (struct usb_device_instance *device,
urb->actual_length = 0;
cp = (char*)urb->buffer;
- dbg_ep0 (2, "%s", USBD_DEVICE_DESCRIPTORS (descriptor_type));
+ dbg_ep0 (2, "%s (%d)", USBD_DEVICE_DESCRIPTORS (descriptor_type), descriptor_type);
switch (descriptor_type) {
case USB_DESCRIPTOR_TYPE_DEVICE:
@@ -276,7 +281,28 @@ static int ep0_get_descriptor (struct usb_device_instance *device,
case USB_DESCRIPTOR_TYPE_ENDPOINT:
serial_printf("USB_DESCRIPTOR_TYPE_ENDPOINT - error not implemented\n");
return -1;
+ /* This really means "Class Specific Descriptor #1 == USB_DT_DFU */
case USB_DESCRIPTOR_TYPE_HID:
+#ifdef CONFIG_USBD_DFU
+ {
+ int bNumInterface =
+ le16_to_cpu(urb->device_request.wIndex);
+
+ /* In runtime mode, we only respond to the DFU INTERFACE,
+ * whereas in DFU mode, we respond for all intrfaces */
+ if (device->dfu_state != DFU_STATE_appIDLE &&
+ device->dfu_state != DFU_STATE_appDETACH ||
+ bNumInterface == CONFIG_USBD_DFU_INTERFACE) {
+
+ /* copy descriptor for this device */
+ copy_config (urb, &device->dfu_cfg_desc->func_dfu,
+ sizeof (struct usb_dfu_func_descriptor),
+ max);
+ } else {
+ return -1;
+ }
+ }
+#else /* CONFIG_USBD_DFU */
{
serial_printf("USB_DESCRIPTOR_TYPE_HID - error not implemented\n");
return -1; /* unsupported at this time */
@@ -304,6 +330,7 @@ static int ep0_get_descriptor (struct usb_device_instance *device,
max);
#endif
}
+#endif /* CONFIG_USBD_DFU */
break;
case USB_DESCRIPTOR_TYPE_REPORT:
{
@@ -405,6 +432,24 @@ int ep0_recv_setup (struct urb *urb)
le16_to_cpu (request->wLength),
USBD_DEVICE_REQUESTS (request->bRequest));
+#ifdef CONFIG_USBD_DFU
+ if ((request->bmRequestType & 0x3f) == USB_TYPE_DFU &&
+ (device->dfu_state != DFU_STATE_appIDLE ||
+ le16_to_cpu(request->wIndex) == CONFIG_USBD_DFU_INTERFACE)) {
+ int rc = dfu_ep0_handler(urb);
+ switch (rc) {
+ case DFU_EP0_NONE:
+ case DFU_EP0_UNHANDLED:
+ break;
+ case DFU_EP0_ZLP:
+ case DFU_EP0_DATA:
+ return 0;
+ case DFU_EP0_STALL:
+ return -1;
+ }
+ }
+#endif /* CONFIG_USBD_DFU */
+
/* handle USB Standard Request (c.f. USB Spec table 9-2) */
if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) {
if(device->device_state <= STATE_CONFIGURED){
@@ -582,7 +627,8 @@ int ep0_recv_setup (struct urb *urb)
device->interface = le16_to_cpu (request->wIndex);
device->alternate = le16_to_cpu (request->wValue);
/*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */
- serial_printf ("DEVICE_SET_INTERFACE.. event?\n");
+ usbd_device_event_irq(device, DEVICE_SET_INTERFACE,
+ (request->wIndex << 16 | request->wValue));
return 0;
case USB_REQ_GET_STATUS:
diff --git a/include/usb_dfu.h b/include/usb_dfu.h
new file mode 100644
index 0000000..adeb51c
--- /dev/null
+++ b/include/usb_dfu.h
@@ -0,0 +1,95 @@
+#ifndef _DFU_H
+#define _DFU_H
+
+/* USB Device Firmware Update Implementation for u-boot
+ * (C) 2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge at openmoko.org>
+ *
+ * based on: USB Device Firmware Update Implementation for OpenPCD
+ * (C) 2006 by Harald Welte <hwelte at hmw-consulting.de>
+ *
+ * This ought to be compliant to the USB DFU Spec 1.0 as available from
+ * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <asm/types.h>
+#include <usbdevice.h>
+#include <usbdescriptors.h>
+#include <usb_dfu_descriptors.h>
+#include <config.h>
+
+/* USB DFU functional descriptor */
+#define DFU_FUNC_DESC { \
+ .bLength = USB_DT_DFU_SIZE, \
+ .bDescriptorType = USB_DT_DFU, \
+ .bmAttributes = USB_DFU_CAN_UPLOAD | USB_DFU_CAN_DOWNLOAD | USB_DFU_MANIFEST_TOL, \
+ .wDetachTimeOut = 0xff00, \
+ .wTransferSize = CONFIG_USBD_DFU_XFER_SIZE, \
+ .bcdDFUVersion = 0x0100, \
+}
+
+/* USB Interface descriptor in Runtime mode */
+#define DFU_RT_IF_DESC { \
+ .bLength = USB_DT_INTERFACE_SIZE, \
+ .bDescriptorType = USB_DT_INTERFACE, \
+ .bInterfaceNumber = CONFIG_USBD_DFU_INTERFACE, \
+ .bAlternateSetting = 0x00, \
+ .bNumEndpoints = 0x00, \
+ .bInterfaceClass = 0xfe, \
+ .bInterfaceSubClass = 0x01, \
+ .bInterfaceProtocol = 0x01, \
+ .iInterface = DFU_STR_CONFIG, \
+}
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#ifndef DFU_NUM_ALTERNATES
+#define DFU_NUM_ALTERNATES 6
+#endif
+
+#define DFU_STR_MANUFACTURER STR_MANUFACTURER
+#define DFU_STR_PRODUCT STR_PRODUCT
+#define DFU_STR_SERIAL STR_SERIAL
+#define DFU_STR_CONFIG (STR_COUNT)
+#define DFU_STR_ALT(n) (STR_COUNT+(n)+1)
+#define DFU_STR_COUNT DFU_STR_ALT(DFU_NUM_ALTERNATES)
+
+#define CONFIG_DFU_CFG_STR "USB Device Firmware Upgrade (DIRTY)"
+#define CONFIG_DFU_ALT0_STR "RAM"
+
+struct _dfu_desc {
+ struct usb_configuration_descriptor ucfg;
+ struct usb_interface_descriptor uif[DFU_NUM_ALTERNATES];
+ struct usb_dfu_func_descriptor func_dfu;
+};
+
+int dfu_init_instance(struct usb_device_instance *dev);
+
+#define DFU_EP0_NONE 0
+#define DFU_EP0_UNHANDLED 1
+#define DFU_EP0_STALL 2
+#define DFU_EP0_ZLP 3
+#define DFU_EP0_DATA 4
+
+extern volatile enum dfu_state *system_dfu_state; /* for 3rd parties */
+
+int dfu_ep0_handler(struct urb *urb);
+
+void dfu_event(struct usb_device_instance *device,
+ usb_device_event_t event, int data);
+
+#endif /* _DFU_H */
diff --git a/include/usb_dfu_descriptors.h b/include/usb_dfu_descriptors.h
new file mode 100644
index 0000000..d4d6a4d
--- /dev/null
+++ b/include/usb_dfu_descriptors.h
@@ -0,0 +1,94 @@
+#ifndef _USB_DFU_H
+#define _USB_DFU_H
+/* USB Device Firmware Update Implementation for OpenPCD
+ * (C) 2006 by Harald Welte <hwelte at hmw-consulting.de>
+ *
+ * Protocol definitions for USB DFU
+ *
+ * This ought to be compliant to the USB DFU Spec 1.0 as available from
+ * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/types.h>
+
+#define USB_DT_DFU 0x21
+
+struct usb_dfu_func_descriptor {
+ u_int8_t bLength;
+ u_int8_t bDescriptorType;
+ u_int8_t bmAttributes;
+#define USB_DFU_CAN_DOWNLOAD (1 << 0)
+#define USB_DFU_CAN_UPLOAD (1 << 1)
+#define USB_DFU_MANIFEST_TOL (1 << 2)
+#define USB_DFU_WILL_DETACH (1 << 3)
+ u_int16_t wDetachTimeOut;
+ u_int16_t wTransferSize;
+ u_int16_t bcdDFUVersion;
+} __attribute__ ((packed));
+
+#define USB_DT_DFU_SIZE 9
+
+#define USB_TYPE_DFU (USB_TYPE_CLASS|USB_RECIP_INTERFACE)
+
+/* DFU class-specific requests (Section 3, DFU Rev 1.1) */
+#define USB_REQ_DFU_DETACH 0x00
+#define USB_REQ_DFU_DNLOAD 0x01
+#define USB_REQ_DFU_UPLOAD 0x02
+#define USB_REQ_DFU_GETSTATUS 0x03
+#define USB_REQ_DFU_CLRSTATUS 0x04
+#define USB_REQ_DFU_GETSTATE 0x05
+#define USB_REQ_DFU_ABORT 0x06
+
+struct dfu_status {
+ u_int8_t bStatus;
+ u_int8_t bwPollTimeout[3];
+ u_int8_t bState;
+ u_int8_t iString;
+} __attribute__((packed));
+
+#define DFU_STATUS_OK 0x00
+#define DFU_STATUS_errTARGET 0x01
+#define DFU_STATUS_errFILE 0x02
+#define DFU_STATUS_errWRITE 0x03
+#define DFU_STATUS_errERASE 0x04
+#define DFU_STATUS_errCHECK_ERASED 0x05
+#define DFU_STATUS_errPROG 0x06
+#define DFU_STATUS_errVERIFY 0x07
+#define DFU_STATUS_errADDRESS 0x08
+#define DFU_STATUS_errNOTDONE 0x09
+#define DFU_STATUS_errFIRMWARE 0x0a
+#define DFU_STATUS_errVENDOR 0x0b
+#define DFU_STATUS_errUSBR 0x0c
+#define DFU_STATUS_errPOR 0x0d
+#define DFU_STATUS_errUNKNOWN 0x0e
+#define DFU_STATUS_errSTALLEDPKT 0x0f
+
+enum dfu_state {
+ DFU_STATE_appIDLE = 0,
+ DFU_STATE_appDETACH = 1,
+ DFU_STATE_dfuIDLE = 2,
+ DFU_STATE_dfuDNLOAD_SYNC = 3,
+ DFU_STATE_dfuDNBUSY = 4,
+ DFU_STATE_dfuDNLOAD_IDLE = 5,
+ DFU_STATE_dfuMANIFEST_SYNC = 6,
+ DFU_STATE_dfuMANIFEST = 7,
+ DFU_STATE_dfuMANIFEST_WAIT_RST = 8,
+ DFU_STATE_dfuUPLOAD_IDLE = 9,
+ DFU_STATE_dfuERROR = 10,
+};
+
+#endif /* _USB_DFU_H */
diff --git a/include/usbdevice.h b/include/usbdevice.h
index 4171636..6c2fc27 100644
--- a/include/usbdevice.h
+++ b/include/usbdevice.h
@@ -33,6 +33,7 @@
#include <common.h>
#include "usbdescriptors.h"
+#include <usb_dfu_descriptors.h>
#define MAX_URBS_QUEUED 5
@@ -467,7 +468,11 @@ typedef struct urb_link {
* function driver to inform it that data has arrived.
*/
+#ifdef CONFIG_USBD_DFU
+#define URB_BUF_SIZE (128+CONFIG_USBD_DFU_XFER_SIZE)
+#else
#define URB_BUF_SIZE 128 /* in linux we'd malloc this, but in u-boot we prefer static data */
+#endif
struct urb {
struct usb_endpoint_instance *endpoint;
@@ -595,6 +600,12 @@ struct usb_device_instance {
unsigned long usbd_rxtx_timestamp;
unsigned long usbd_last_rxtx_timestamp;
+#ifdef CONFIG_USBD_DFU
+ const struct usb_device_descriptor *dfu_dev_desc;
+ const struct _dfu_desc *dfu_cfg_desc;
+ enum dfu_state dfu_state;
+ u_int8_t dfu_status;
+#endif
};
/* Bus Interface configuration structure
@@ -624,6 +635,8 @@ extern char *usbd_device_status[];
extern char *usbd_device_requests[];
extern char *usbd_device_descriptors[];
+extern struct usb_string_descriptor **usb_strings;
+
void urb_link_init (urb_link * ul);
void urb_detach (struct urb *urb);
urb_link *first_urb_link (urb_link * hd);
--
1.7.7.1
More information about the U-Boot
mailing list