From patchwork Thu Nov 3 15:56:36 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Schmidt X-Patchwork-Id: 123464 X-Patchwork-Delegate: marek.vasut@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id E7848B6F75 for ; Fri, 4 Nov 2011 02:57:04 +1100 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 835A42853B; Thu, 3 Nov 2011 16:57:01 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id IbQsPq8od6sR; Thu, 3 Nov 2011 16:57:01 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id CEA95285A2; Thu, 3 Nov 2011 16:56:58 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 93D9D29011 for ; Thu, 3 Nov 2011 16:56:55 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id QQZvU55RF1lk for ; Thu, 3 Nov 2011 16:56:54 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from sirius.lasnet.de (sirius.lasnet.de [78.47.116.19]) by theia.denx.de (Postfix) with ESMTPS id 200392917C for ; Thu, 3 Nov 2011 16:56:50 +0100 (CET) Received: from [2001:638:602:1183:21f:16ff:fe0d:7d41] (helo=excalibur.local) by sirius.lasnet.de with esmtpsa (Cipher TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.69 #1) id 1RLzeh-0005mx-Bo by authid with srv_auth_cram_md5; Thu, 03 Nov 2011 16:56:49 +0100 Received: from stefan by excalibur.local with local (Exim 4.77) (envelope-from ) id 1RLzeg-0008Hj-Uk; Thu, 03 Nov 2011 16:56:38 +0100 From: Stefan Schmidt To: u-boot@lists.denx.de Date: Thu, 3 Nov 2011 16:56:36 +0100 Message-Id: <1320335796-31812-2-git-send-email-stefan@datenfreihafen.org> X-Mailer: git-send-email 1.7.7.1 In-Reply-To: <1320335796-31812-1-git-send-email-stefan@datenfreihafen.org> References: <1320335796-31812-1-git-send-email-stefan@datenfreihafen.org> Cc: andrzej.p@samsung.com Subject: [U-Boot] [PATCH 1/1] usb/gadget: Add Device Firmware Upgrade (DFU) support. X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.9 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de 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 --- 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 +#include +#include #include #include #include @@ -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 #include +#include #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 + * + * based on existing SAM7DFU code from OpenPCD: + * (C) Copyright 2006 by Harald Welte + * + * 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 +#if defined(CONFIG_USBD_DFU) + +/* FIXME disbale debug */ +#define DEBUG + +#include +DECLARE_GLOBAL_DATA_PTR; /* FIXME needed? */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +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@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 #include +DECLARE_GLOBAL_DATA_PTR; + +#ifdef CONFIG_USBD_DFU +#include +#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 + * + * based on: USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 by Harald Welte + * + * 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 +#include +#include +#include +#include + +/* 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 + * + * 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 + +#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 #include "usbdescriptors.h" +#include #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);