From: Marcel <korg...@home.nl> USB DFU driver cleaning phase1
USB DFU driver cleaning phase2 USB DFU driver cleaning phase3 USB DFU driver cleaning phase4 Signed-off-by: Marcel <korg...@home.nl> --- common/update_dfu.c | 89 +++ doc/README.dfu | 129 ++++ drivers/usb/gadget/Makefile | 9 + drivers/usb/gadget/usbdfu.c | 1336 +++++++++++++++++++++++++++++++++++++++++ include/usb_dfu.h | 128 ++++ include/usb_dfu_descriptors.h | 100 +++ 6 files changed, 1791 insertions(+), 0 deletions(-) create mode 100644 common/update_dfu.c create mode 100644 doc/README.dfu create mode 100644 drivers/usb/gadget/usbdfu.c create mode 100644 include/usb_dfu.h create mode 100644 include/usb_dfu_descriptors.h diff --git a/common/update_dfu.c b/common/update_dfu.c new file mode 100644 index 0000000..f1ceccf --- /dev/null +++ b/common/update_dfu.c @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2011 Marcel Janssen, Admesy B.V. + * + * 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 <common.h> +DECLARE_GLOBAL_DATA_PTR; + +#include <malloc.h> +#include <linux/types.h> +#include <linux/list.h> +#include <asm-generic/errno.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <usb_dfu.h> + +#include <command.h> +#ifdef CONFIG_USB_GADGET_ATMEL_USBA +#include <usb/atmel_usba_udc.h> +#endif +#ifdef CONFIG_USB_GADGET_AT91 +#include <usb/at91_udc.h> +#endif +#include <asm/arch/gpio.h> + +#ifdef CONFIG_USB_GADGET_ATMEL_USBA +struct platform_data dfubrd = { + .board = { + .vbus_pin = AT91_PIN_PC0, + .pullup_pin = 1, + }, + .udc_clk = AT91SAM9G45_ID_UDPHS, +}; +#endif + +int dfu_loop(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + int rcv; + + dfu_finished = 0; + + /* initialize the USBD controller */ +#ifdef CONFIG_USB_GADGET_ATMEL_USBA + usba_udc_probe(&dfubrd); + udelay(100000); +#endif +#ifdef CONFIG_USB_GADGET_AT91 + at91udc_probe(&dfubrd); + udelay(100000); +#endif + if (usb_dfu_init() == 0) { + while (1) { + /* Handle control-c and timeouts */ + rcv = usb_gadget_handle_interrupts(); + if (rcv) + if (ctrlc()) + goto dfu_end; + } + return 0; + } + return -1; +dfu_end: + return -1; +} + +U_BOOT_CMD( + dfu, CONFIG_SYS_MAXARGS, 1, dfu_loop, + "Start DFU function", + "No params, see README.dfu" +); diff --git a/doc/README.dfu b/doc/README.dfu new file mode 100644 index 0000000..363c7a2 --- /dev/null +++ b/doc/README.dfu @@ -0,0 +1,129 @@ +USBD DFU mode + +Initially written by Marcel Janssen, Admesy B.V. +Based on parts from OpenMoko, ether.c and update.c + + +======================================== + +This describes the DFU implementation in u-boot. + +The implementation works with dfu-utils to upgrade NAND partitions defined by +mtdparts. +The board configuration file needs serveral CONFIG options to be set. +DFU is implemented to be executed as a command "dfu" (common/update_dfu.c). +This command should start the USB device controller and the DFU driver. +A typical implementation would be that a script is executed, that will check +whether DFU should be started. If so, it can execute 'dfu" and the device will +announce itself to the host as a DFU capable device. +dfu-util can than be used to upgrade the partitions defined by mtdparts. + +Description of flow : +dfu-utils sets the alternate interface which corresponds to the selected +partition. +The file (uImage, rootfs.arm.jffs2) is loaded fully to RAM first. +U-boot nand routines are used to write from RAM to NAND. + +LED usage : +Status LED's can be defined to show DFU action. +Define the RED and GREEN leds to make this happen. + +Initial testing example : +This was done on the in-circuit icnova_sam9g45 board. +This board uses atmel_usbd_udc.c + +======================================== + + + +To make DFU work you need a working USB controller, for example at91_udc or +atmel_usba_udc. Make sure to set it in the board config file. + +======================================== +USBD CONFIG options +---------------------------------------- + +#define CONFIG_USB_GADGET +#define CONFIG_USB_GADGET_ATMEL_USBA (or #define CONFIG_USB_GADGET_AT91_UDC ) +#define CONFIG_USB_GADGET_DUALSPEED + +---------------------------------------- +USBD CONFIG options end +======================================== + + +======================================== +DFU CONFIG options +---------------------------------------- + +#define CONFIG_USBD_DFU 1 +#ifdef CONFIG_USBD_DFU +#define CONFIG_USBD_VENDORID 0x23CF /* Admesy */ +#define CONFIG_USBD_PRODUCTID_DFU 0x0100 /* donated number */ +#define CONFIG_USBD_MANUFACTURER "Admesy" +#define CONFIG_USBD_PRODUCT_NAME "Admesy DFU 001" +#define CONFIG_USBD_DFU_XFER_SIZE 4096 /* Buffer size */ +#define CONFIG_USBD_DFU_INTERFACE 0 +#define DFU_NUM_ALTERNATES 3 /* 3 partitions */ +#define LOAD_ADDR ((unsigned char *)0x70400000) /* RAM address to use */ +#endif + +---------------------------------------- +DFU CONFIG options end +======================================== + + + +In order to make DFU work with dfu-utils, mtdparts need to be defined. +See the example futher below on how to do this. + +======================================== +mtdparts example with dfu-utils +---------------------------------------- + +mtdparts add nand0 0x2000000 kernel +mtdparts add nand0 0x1000000@0x00200000 root +mtdparts add nand0 0xEE00000@0x01200000 data +saveenv + +Your mtdparts than should look like this : + +board> mtdparts +device nand0 <nand.0>, # parts = 3 + #: name size offset mask_flags + 0: kernel 0x00200000 0x00000000 0 + 1: root 0x01000000 0x00200000 0 + 2: data 0x0ee00000 0x01200000 0 + +active partition: nand0,0 - (kernel) 0x00200000 @ 0x00000000 + + +After the mtdparts have been defined, dfu-utils can be used to upgrade the +kernel and root partition. +Make sure you have read/write access to the DFU device ! + +cd dfu-utils/src +./dfu-util -a0 -D uImage +./dfu-util -a1 -D rootfs.arm.jffs2 -R + +---------------------------------------- +mtdparts example end +======================================== + + + +Possible changes for the near future : +1) integrate DFU and Ethernet and/or tty by making it a composite driver. +2) allow partitions larger than RAM by implementing per-buffer NAND writing. + Openmoko does this, but I didn't need it and liked to write to RAM first. +3) Allow DFU to flash NOR (could be added to handle_dnload ) + + + + + + + + + + diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 024844d..d1ba030 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -30,6 +30,14 @@ ifdef CONFIG_USB_ETHER COBJS-y += ether.o epautoconf.o config.o usbstring.o COBJS-$(CONFIG_USB_GADGET_AT91) += at91_udc.o COBJS-$(CONFIG_USB_GADGET_ATMEL_USBA) += atmel_usba_udc.o + +else +ifdef CONFIG_USBD_DFU +COBJS-y += usbdfu.o epautoconf.o config.o usbstring.o + +COBJS-$(CONFIG_USB_GADGET_AT91) += at91_udc.o +COBJS-$(CONFIG_USB_GADGET_ATMEL_USBA) += atmel_usba_udc.o + else # Devices not related to the new gadget layer depend on CONFIG_USB_DEVICE ifdef CONFIG_USB_DEVICE @@ -42,6 +50,7 @@ COBJS-$(CONFIG_PXA27X) += pxa27x_udc.o COBJS-$(CONFIG_SPEARUDC) += spr_udc.o endif endif +endif COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/usb/gadget/usbdfu.c b/drivers/usb/gadget/usbdfu.c new file mode 100644 index 0000000..65f334a --- /dev/null +++ b/drivers/usb/gadget/usbdfu.c @@ -0,0 +1,1336 @@ +/* + * (C) 2011 Marcel Janssen, Admesy B.V. + * (C) 2007 by OpenMoko, Inc. + * Author: Harald Welte <lafo...@openmoko.org> + * + * based on existing SAM7DFU code from OpenPCD: + * (C) Copyright 2006 by Harald Welte <hwe...@hmw-consulting.de> + * + * 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 + * + * TODO: + * - make NAND support reasonably self-contained and put in apropriate + * ifdefs + * - add some means of synchronization, i.e. block commandline access + * while DFU transfer is in progress, and return to commandline once + * we're finished + * - add VERIFY support after writing to flash + * - sanely free() resources allocated during first uppload/download + * request when aborting + * - sanely free resources when another alternate interface is selected + * + * Maybe: + * - add something like uImage or some other header that provides CRC + * checking? + * - make 'dnstate' attached to 'struct dfu_dev' + */ + +#include <config.h> +#if defined(CONFIG_USBD_DFU) + +#include <common.h> +DECLARE_GLOBAL_DATA_PTR; + +#include <malloc.h> +#include <linux/types.h> +#include <linux/list.h> +#include <asm-generic/errno.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include "gadget_chips.h" + +#include <usb_dfu.h> +#include <usb_dfu_descriptors.h> +#include <linux/ctype.h> +#include <linux/porting-compat.h> + +#include <nand.h> +#include <jffs2/load_kernel.h> + +extern struct list_head devices; + +#define RET_NOTHING 0 +#define RET_ZLP 1 +#define RET_STALL 2 + +#ifndef CONFIG_USBD_DFU_XFER_SIZE +#define CONFIG_USBD_DFU_XFER_SIZE 4096 +#endif + +/* this should be done differently */ +#define EP0_MAX_PACKET_SIZE 64 /* MUSB_EP0_FIFOSIZE */ + +unsigned char ledcount; + +#define GFP_ATOMIC ((gfp_t) 0) + +#ifdef CONFIG_USB_GADGET_DUALSPEED +#define DEVSPEED USB_SPEED_HIGH +#else +#define DEVSPEED USB_SPEED_FULL +#endif + +#define USB_CONNECT_TIMEOUT (30 * CONFIG_SYS_HZ) +enum dfu_state *system_dfu_state; /* for 3rd parties */ + +char rtm_dfu; + +/* + * Some systems will want different product identifers published in the + * device descriptor, either numbers or strings or both. These string + * parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ +#define DRIVER_DESC "USBD-DFU device" +/* usually only one device should be in DFU mode, this may be fixed */ +#define FIXED_SERIAL "1234567" +static ushort bcdDevice; +#if defined(CONFIG_USBD_MANUFACTURER) +static char *iManufacturer = CONFIG_USBD_MANUFACTURER; +#else +static char *iManufacturer = "U-boot-dfu"; +#endif +static char *iProduct; +static char *iSerialNumber; + +#ifndef CONFIG_USBD_PRODUCTID_DFU +#define #error YOU need to define a USBD product ID for DFU +#endif + +#define LOAD_ADDR ((unsigned char *)0x70400000) + +static struct dfu_dev l_dfudev; +static struct usb_gadget_driver dfu_driver; +static u8 control_req[CONFIG_USBD_DFU_XFER_SIZE]; + + +struct usb_device_descriptor +dfu_dev_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0100), + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = EP0_MAX_PACKET_SIZE, + .idVendor = __constant_cpu_to_le16 + (CONFIG_USBD_VENDORID), + .idProduct = __constant_cpu_to_le16 + (CONFIG_USBD_PRODUCTID_DFU), + .bcdDevice = 0x0000, + .iManufacturer = DFU_STR_MANUFACTURER, + .iProduct = DFU_STR_PRODUCT, + .iSerialNumber = DFU_STR_SERIAL, + .bNumConfigurations = 0x01, +}; + +static struct usb_config_descriptor +dfu_config = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + + /*compute wTotalLength on the fly */ + .wTotalLength = USB_DT_CONFIG_SIZE + + USB_DT_INTERFACE_SIZE + USB_DT_DFU_SIZE, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = DFU_STR_CONFIG, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0x60, +}; + +static struct usb_interface_descriptor +control_intf = { + .bLength = sizeof control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = CONFIG_USBD_DFU_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_APP_SPEC, + .bInterfaceSubClass = USB_DFU_SUBCLASS, + .bInterfaceProtocol = 1, + .iInterface = DFU_STR_CONFIG, +}; + +static struct usb_dfu_func_descriptor +dfu_func = { + .bLength = USB_DT_DFU_SIZE, + .bDescriptorType = USB_DT_DFU, + .bmAttributes = USB_DFU_CAN_UPLOAD | USB_DFU_CAN_DOWNLOAD, + .wDetachTimeOut = 0xff00, + .wTransferSize = CONFIG_USBD_DFU_XFER_SIZE, + .bcdDFUVersion = 0x0100, +}; + +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 = USB_CONFIG_ATT_ONE | + USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 200, + }, + .func_dfu = DFU_FUNC_DESC, +}; + +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_APP_SPEC, + .bNumConfigurations = 1, +}; + + +static const struct usb_descriptor_header *hs_rtm_function[3] = { + (struct usb_descriptor_header *) &control_intf, + (struct usb_descriptor_header *) &dfu_func, + NULL, +}; + +static const struct usb_descriptor_header *hs_dfu_function[5] = { + (struct usb_descriptor_header *) &dfu_cfg_descriptor.uif[0], + (struct usb_descriptor_header *) &dfu_cfg_descriptor.uif[1], + (struct usb_descriptor_header *) &dfu_cfg_descriptor.uif[2], + (struct usb_descriptor_header *) &dfu_cfg_descriptor.func_dfu, + NULL, +}; + +struct dnload_state { + nand_info_t *nand; + struct part_info *part; + unsigned int part_net_size; /* net size (excl. bad blocks) */ + nand_erase_options_t erase_opts; + nand_write_options_t write_opts; + nand_read_options_t read_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 on flash page size */ +}; + +static struct dnload_state _dnstate; + +static struct part_info *get_partition_nand(int idx) +{ + struct mtd_device *dev; + struct part_info *part; + struct list_head *dentry; + struct list_head *pentry; + int i; + + if (mtdparts_init()) + return NULL; + + list_for_each(dentry, &devices) { + dev = list_entry(dentry, struct mtd_device, link); + if (dev->id->type == MTD_DEV_TYPE_NAND) { + i = 0; + list_for_each(pentry, &dev->parts) { + if (i == idx) { + part = list_entry(pentry, + struct part_info, link); + return part; + } + i++; + } + return NULL; + } + } + return NULL; +} + +/* descriptors that are built on-demand */ +static char manufacturer[50]; +static char product_desc[40] = CONFIG_USBD_PRODUCT_NAME; +static char serial_number[20] = FIXED_SERIAL; + +/* static strings, in UTF-8 */ +static struct usb_string strings[] = { + { DFU_STR_MANUFACTURER, manufacturer, }, + { DFU_STR_PRODUCT, product_desc, }, + { DFU_STR_SERIAL, serial_number, }, + { DFU_STR_CONFIG, "USB DFU Config", }, + { DFU_STR_ALT1, "DFU part1", }, + { DFU_STR_ALT2, "DFU part2", }, + { DFU_STR_ALT3, "DFU part3", }, + { DFU_STR_ALT4, "DFU part4", }, + { DFU_STR_ALT5, "DFU part5", }, + { DFU_STR_ALT6, "DFU part6", }, + { } /* end of list */ +}; + +struct usb_gadget_strings stringtab = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + +/*============================================================================*/ + +/* + * strlcpy - Copy a %NUL terminated string into a sized buffer + * @dest: Where to copy the string to + * @src: Where to copy the string from + * @size: size of destination buffer + * + * Compatible with *BSD: the result is always a valid + * NUL-terminated string that fits in the buffer (unless, + * of course, the buffer size is zero). It does not pad + * out the result like strncpy() does. + */ +size_t strlcpy(char *dest, const char *src, size_t size) +{ + size_t ret = strlen(src); + + if (size) { + size_t len = (ret >= size) ? size - 1 : ret; + memcpy(dest, src, len); + dest[len] = '\0'; + } + return ret; +} + +/* + * one config, two interfaces: control, data. + * complications: class descriptors, and an altsetting. + */ +static int +config_buf(struct usb_gadget *g, u8 *buf, u8 type, unsigned index) +{ + int len; + const struct usb_config_descriptor *config; + const struct usb_descriptor_header **function; + int hs = 0; + + debug("Set configuration - type %d - index %d\n", type, index); + + if (gadget_is_dualspeed(g)) { + hs = (g->speed == USB_SPEED_HIGH); + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + } + + if (index >= dfu_dev_descriptor.bNumConfigurations) + return -EINVAL; + + switch (type) { + case 2: + { + config = &dfu_config; + if (rtm_dfu == 0) + function = hs_rtm_function; + else + function = hs_dfu_function; + } + break; + } + len = usb_gadget_config_buf(config, + buf, CONFIG_USBD_DFU_XFER_SIZE, function); + if (len < 0) + return len; + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + +static int +set_dfu_config(struct dfu_dev *dev, gfp_t gfp_flags) +{ + const struct usb_config_descriptor *config; + + printf("Set DFU config\n"); + + config = &dfu_cfg_descriptor.ucfg; + + return 0; +} + +static int handle_dnload(struct usb_gadget *gadget, + u_int16_t val, u_int16_t len, int first) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_request *req = dev->req; + struct dnload_state *ds = &_dnstate; + int rc; + ulong addr; + size_t rwsize; + + debug("download(len=%u, first=%u)\n", len, first); + + if (len > CONFIG_USBD_DFU_XFER_SIZE) { + /* Too big. Not that we'd really care, but it's a + * DFU protocol violation */ + printf("length exceeds flash page size\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + + if (first) { + /* Make sure that we have a valid mtd partition table */ + char *mtdp = getenv("mtdparts"); + if (mtdp) + printf("Valid MTD partitions found\n"); + /*this used to be in the Openmoko driver */ + /*if (!mtdp) + /*run_command("dynpart", 0); */ + else { + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + } + + if (len == 0) { + debug("zero-size write -> MANIFEST_SYNC "); + dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC; + + /* cleanup */ + switch (dev->alternate) { + char buf[12]; + /* we don't actually handle partitions differently + * the original Openmoko driver did use the alternate + * setting to do different things we have no need for + * that a big difference is that our driver first reads + * the full firmware to RAM and than writes it to flash + */ + default: + sprintf(buf, "0x%08x", ds->ptr - ds->buf); + addr = (ulong)LOAD_ADDR; + rwsize = ds->ptr - ds->buf; + printf("Filesize = %s\n", buf); + setenv("filesize", buf); + rc = 0; + rc = mtdparts_init(); + if (rc) { + printf("Error initilizing mtdparts\n"); + break; + } + ds->part = get_partition_nand(dev->alternate); + if (ds->part == NULL) { + printf("Error reading partition info\n"); + break; + } + + ds->erase_opts.offset = ds->part->offset; + ds->erase_opts.length = ds->part->size; + if (!rc && !strcmp(ds->part->name, "root")) + ds->erase_opts.jffs2 = 1; + else + ds->erase_opts.jffs2 = 0; + ds->erase_opts.quiet = 0; + ds->erase_opts.spread = 1; + ds->erase_opts.scrub = 0; /* do not allow this */ + ds->nand = &nand_info[ds->part->dev->id->num]; + + printf("Using partition : %s ,", ds->part->name); + if (ds->erase_opts.jffs2) + printf(" with jffs2 option\n"); + else + printf(" without jffs2 option\n"); + printf("Partition size=%lx offset=%lx\n", + (unsigned long)ds->part->size, + (unsigned long)ds->part->offset); +#ifdef CONFIG_GREEN_LED + green_LED_off(); +#endif +#ifdef CONFIG_RED_LED + red_LED_off(); +#endif + rc = nand_erase_opts(ds->nand, &ds->erase_opts); + if (rc) { + printf("NAND erase failed\n"); + break; + } else + printf("NAND erased succesfully\n"); +#ifdef CONFIG_GREEN_LED + green_LED_on(); +#endif +#ifdef CONFIG_RED_LED + red_LED_off(); +#endif + rc = nand_write_skip_bad(ds->nand, + ds->part->offset, + &rwsize, + (u_char *)addr); + if (rc) { + printf("NAND write failed\n"); + break; + } else + printf("NAND Written succesfully\n"); +#ifdef CONFIG_GREEN_LED + green_LED_on(); +#endif +#ifdef CONFIG_RED_LED + red_LED_on(); +#endif + break; + + } + return RET_ZLP; + } + + if (req->length != len) { + printf("req->length(%u) != len(%u) ?!? ", + req->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 != LOAD_ADDR) { + printf("free buffer\n"); + free(ds->buf); + ds->buf = ds->_buf; + } + + debug("alternate %d , first = %d\n", dev->alternate, first); + switch (dev->alternate) { + default: + if (first) { + ds->buf = LOAD_ADDR; + ds->ptr = ds->buf; + printf("Starting DFU DOWNLOAD to RAM %p\n", ds->ptr); + } + memcpy(ds->ptr, req->buf, len); + ds->ptr += len; + debug("data in buf %x, next address = %p\n", + *(char *)req->buf, ds->ptr); + break; + } + debug("Copy finished at %p\n", ds->ptr); + return RET_ZLP; +} + +static int handle_upload(struct usb_gadget *gadget, + u_int16_t val, u_int16_t len, int first) +{ + struct dfu_dev *dev = &l_dfudev; + + printf("upload(val=0x%02x, len=%u, first=%u) ", val, len, first); + + if (len > CONFIG_USBD_DFU_XFER_SIZE) { + /* Too big */ + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + /*FIXME : maybe we need this */ + /* udc_ep0_send_stall(); */ + debug("Error: Transfer size > CONFIG_USBD_DFU_XFER_SIZE "); + return -EINVAL; + } + + switch (dev->alternate) { + default: + /* this needs to be implemented */ + break; + } + + printf("returning len=%u\n", len); + return len; +} + +static void handle_getstatus(struct usb_gadget *gadget, int max) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_request *req = dev->req; + struct dfu_status *dstat = (struct dfu_status *) req->buf; + + switch (dev->dfu_state) { + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: + debug("DNLOAD_IDLE "); + dev->dfu_state = DFU_STATE_dfuDNLOAD_IDLE; + break; + case DFU_STATE_dfuMANIFEST_SYNC: + break; + default: + break; + } + + /* send status response */ + dstat->bStatus = dev->dfu_status; + dstat->bState = dev->dfu_state; + dstat->iString = 0; + /* FIXME: set dstat->bwPollTimeout */ + req->length = MIN(sizeof(*dstat), max); + + debug("status : bStatus=0x%x bState=0x%x\n", + dstat->bStatus, dstat->bState); + + /* we don't need to explicitly send data here, will + * be done by the original caller! */ +} + +static void handle_getstate(struct usb_gadget *gadget, int max) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_request *req = dev->req; + + printf("getstate\n"); + + if (!req->buf || req->length < sizeof(u_int8_t)) { + debug("invalid ctrl! "); + return; + } + + *(char *)(req->buf) = (char)dev->dfu_state & 0xff; + req->length = sizeof(u_int8_t); +} + +/* + * change our operational config. must agree with the code + * that returns config descriptors, and altsetting code. + */ +static int dfu_set_config(struct dfu_dev *dev, unsigned number, + gfp_t gfp_flags) +{ + int result = 0; + struct usb_gadget *gadget = dev->gadget; + + if (gadget_is_sa1100(gadget) + && dev->config + && dev->tx_qlen != 0) { + /* tx fifo is full, but we can't clear it...*/ + error("can't change configurations"); + return -ESPIPE; + } + + if (rtm_dfu == 0) { + if (number > 0) { + if (number <= DFU_NUM_ALTERNATES) { + printf("DFU set config : %d\n", number); + result = set_dfu_config(dev, gfp_flags); + } else + result = -EINVAL; + } + } + char *speed; + unsigned power; + + power = 2 * dfu_cfg_descriptor.ucfg.bMaxPower; + usb_gadget_vbus_draw(gadget, power); + + switch (gadget->speed) { + case USB_SPEED_FULL: + speed = "full"; break; +#ifdef CONFIG_USB_GADGET_DUALSPEED + case USB_SPEED_HIGH: + speed = "high"; break; +#endif + default: + speed = "?"; break; + } + + dev->config = number; + printf("%s speed config #%d: %d mA\n", + speed, number, power); + + return result; +} + +static void dfu_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + debug("dfu_setup_complete\n"); + + if (req->status || req->actual != req->length) + printf("setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +static void dfu_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_gadget *gadget = dev->gadget; + const struct usb_ctrlrequest *ctrl = dev->ctrl; + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + char ret = RET_NOTHING; + int value = -EOPNOTSUPP; + + debug("dfu_status_complete , status = %d\n", dev->dfu_state); +#ifdef CONFIG_GREEN_LED + if (ledcount < 127) + green_LED_on(); + else + green_LED_off(); + ledcount -= 5; +#endif + switch (dev->dfu_state) { + case DFU_STATE_dfuIDLE: + debug("handle first download\n"); + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + ret = handle_dnload(gadget, wValue, wLength, 1); + value = req->length; + break; + case DFU_STATE_dfuDNLOAD_IDLE: + debug("handle DFU_STATE_dfuDNLOAD_IDLE\n"); + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + ret = handle_dnload(gadget, wValue, wLength, 0); + value = req->length; + break; + case DFU_STATE_appIDLE: + break; + case DFU_STATE_dfuDNLOAD_SYNC: + debug("handle sequential download\n"); + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + ret = handle_dnload(gadget, wValue, wLength, 0); + value = req->length; + break; + case DFU_STATE_dfuDNBUSY: + break; + case DFU_STATE_dfuMANIFEST_SYNC: + break; + case DFU_STATE_dfuMANIFEST: + break; + case DFU_STATE_dfuMANIFEST_WAIT_RST: + break; + case DFU_STATE_dfuUPLOAD_IDLE: + break; + case DFU_STATE_appDETACH: + break; + case DFU_STATE_dfuERROR: + break; + } +} + +int dfu_ep0_handler(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_request *req = dev->req; + int rc, complete_status; + char ret = RET_NOTHING; + int value = -EOPNOTSUPP; + u8 requesttype = le16_to_cpu(ctrl->bRequestType); + u8 request = le16_to_cpu(ctrl->bRequest); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + u16 wIndex = le16_to_cpu(ctrl->wIndex); + + debug("dfu_ep0 (req=0x%x, bRequest=0x%x wVal=0x%x, wLen=%u)\n", + req, request, wValue, wLength); + + /* Handle vendor specific code + * this is an example how to reset u-boot by sending a + * control message. This can be useful in case + * the USB reset fails for whatever reason. + */ + if ((requesttype == 0x41) && (request == 0x08)) { + printf("Device reset called\n"); + do_reset(NULL, 0, 0, NULL); + } + + dev->ctrl = ctrl; + complete_status = 0; + req->complete = dfu_setup_complete; + switch (request) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + break; + + switch (wValue >> 8) { + + case USB_DT_DEVICE: + debug("USB_DT_DEVICE\n"); + rtm_dfu = 0; + value = min(wLength, (u16) sizeof dfu_dev_descriptor); + memcpy(req->buf, &dfu_dev_descriptor, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget)) + break; + debug("USB_DT_DEVICE_QUALIFIER not supported\n"); + value = min(wLength, (u16) sizeof dev_qualifier); + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + debug("USB_DT_OTHER_SPEED_CONFIG\n"); + if (!gadget_is_dualspeed(gadget)) + break; + /* FALLTHROUGH */ + case USB_DT_CONFIG: + debug("USB_DT_CONFIG\n"); + rtm_dfu = 1; + value = config_buf(gadget, req->buf, + wValue >> 8, + wValue & 0xff); + if (value >= 0) + value = min(wLength, (u16) value); + break; + + case USB_DT_STRING: + debug("USB_DT_STRING\n"); + value = usb_gadget_get_string(&stringtab, + wValue & 0xff, req->buf); + + if (value >= 0) + value = min(wLength, (u16) value); + break; + + case USB_DT_INTERFACE: + debug("USB_DT_INTERFACE is not needed\n"); + break; + } + break; + + case USB_REQ_SET_CONFIGURATION: + debug("USB_REQ_SET_CONFIGURATION %d\n", wValue); + if (wValue >= 1) + value = dfu_set_config(dev, wValue, GFP_ATOMIC); + if (req->status != -ECONNRESET) + dev->dfu_started = 1; + dev->dfu_state = DFU_STATE_dfuIDLE; + break; + case USB_REQ_GET_CONFIGURATION: + debug("USB_REQ_GET_CONFIGURATION %d\n", wValue); + if (ctrl->bRequestType != USB_DIR_IN) + break; + *(u8 *)req->buf = dev->config; + value = min(wLength, (u16) 1); + break; + + case USB_REQ_SET_INTERFACE: + dev->interface = le16_to_cpu(wIndex); + dev->alternate = le16_to_cpu(wValue); + debug("SET_INTERFACE(%u,%u) old_state = %u\n", + dev->interface, dev->alternate, + dev->dfu_state); + req->complete = dfu_setup_complete; + value = 0; + break; + + case USB_REQ_GET_INTERFACE: + debug("USB_REQ_GET_INTERFACE\n"); + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) + || !dev->config + || wIndex > 1) + break; + if (wIndex != 0) + break; + + if (wIndex != 1) + *(u8 *)req->buf = 0; + else + *(u8 *)req->buf = 1 ; + value = min(wLength, (u16) 1); + break; + + default: + debug("DFU State = %d\n", dev->dfu_state); + + switch (dev->dfu_state) { + + case DFU_STATE_appIDLE: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_DETACH: + dev->dfu_state = DFU_STATE_appDETACH; + ret = RET_ZLP; + goto out; + break; + default: + ret = RET_STALL; + goto out; + } + break; + case DFU_STATE_appDETACH: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + default: + dev->dfu_state = DFU_STATE_appIDLE; + ret = RET_STALL; + goto out; + break; + } + /* FIXME: implement timer to return to appIDLE */ + value = req->length; + break; + case DFU_STATE_dfuIDLE: + switch (request) { + case USB_REQ_DFU_DNLOAD: + if (wLength == 0) { + printf("first packet can not be zero length\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + debug("idle DNL : length %x, wValue %x\n", + wLength, wValue); + req->length = wLength; + complete_status = 1; + break; + case USB_REQ_DFU_UPLOAD: + dev->dfu_state = DFU_STATE_dfuUPLOAD_IDLE; + handle_upload(gadget, wValue, wLength, 1); + value = req->length; + break; + case USB_REQ_DFU_ABORT: + /* no zlp? */ + ret = RET_ZLP; + goto out; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + 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; + value = 0; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + break; + } + debug("DFU packet length = %d bRequest=0x%x\n", + req->length, ctrl->bRequest); + value = req->length; + break; + case DFU_STATE_dfuDNLOAD_SYNC: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + /* FIXME: state transition depending + * on block completeness + */ + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + break; + case DFU_STATE_dfuDNBUSY: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + /* FIXME: only accept getstatus if bwPollTimeout + * has elapsed */ + handle_getstatus(gadget, wLength); + value = req->length; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + break; + case DFU_STATE_dfuDNLOAD_IDLE: + switch (request) { + case USB_REQ_DFU_DNLOAD: + if (wLength == 0) + debug("We finished the download\n"); + debug("idle DNL 2: length %x, wValue %x , state = %x\n", + wLength, wValue, dev->dfu_state); + req->length = wLength; + complete_status = 1; + break; + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + ret = RET_ZLP; + value = 0; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + value = 0; + break; + } + debug("DFU packet length = %d bRequest=0x%x\n", + req->length, ctrl->bRequest); + value = req->length; + break; + case DFU_STATE_dfuMANIFEST_SYNC: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + /* We're MainfestationTolerant */ + dev->dfu_state = DFU_STATE_dfuIDLE; + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + value = 0; + 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 (request) { + case USB_REQ_DFU_UPLOAD: + /* state transition if less data then requested */ + rc = handle_upload(gadget, wValue, wLength, 0); + if (rc >= 0 && rc < wLength) + dev->dfu_state = DFU_STATE_dfuIDLE; + value = req->length; + break; + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + /* no zlp? */ + ret = RET_ZLP; + value = 0; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + value = 0; + break; + } + break; + case DFU_STATE_dfuERROR: + switch (request) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(gadget, wLength); + value = req->length; + break; + case USB_REQ_DFU_CLRSTATUS: + debug("Clear DFU status\n"); + dev->dfu_state = DFU_STATE_dfuIDLE; + dev->dfu_status = DFU_STATUS_OK; + /* no zlp? */ + ret = RET_ZLP; + value = 0; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + value = 0; + break; + } + break; + default: + printf("unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + return DFU_EP0_UNHANDLED; + break; + } + + } + /* respond with data transfer before status phase? */ + if (value >= 0) { + debug("respond with data transfer before status phase\n"); + req->length = value; + req->zero = value < wLength + && (value % gadget->ep0->maxpacket) == 0; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + debug("ep_queue %d req%02x.%02x v%04x i%04x l%d\n", + req->length, ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + if (value < 0) { + debug("ep_queue --> %d\n", value); + req->status = 0; + dfu_setup_complete(gadget->ep0, req); + } + } + + if (complete_status) + req->complete = dfu_status_complete; + + return DFU_EP0_DATA; + +out: + debug("new_state = %u, ret = %u\n", dev->dfu_state, ret); + + /*FIXME the following code needs a review */ + switch (ret) { + case RET_ZLP: + debug("ZERO LENGTH packet\n"); + req->length = 0; + req->zero = value < wLength + && (value % gadget->ep0->maxpacket) == 0; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + req->complete = dfu_status_complete; + return DFU_EP0_ZLP; + break; + case RET_STALL: + req->complete = dfu_status_complete; + return DFU_EP0_STALL; + break; + case RET_NOTHING: + break; + } + return DFU_EP0_DATA; +} + +static void dfu_unbind(struct usb_gadget *gadget) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + + debug("%s...\n", __func__); + + /* we've already been disconnected ... no i/o is active */ + if (dev->req) { + usb_ep_free_request(gadget->ep0, dev->req); + dev->req = NULL; + } + + dev->gadget = NULL; + set_gadget_data(gadget, NULL); +} + +static int dfu_bind(struct usb_gadget *gadget) +{ + struct dfu_dev *dev = &l_dfudev; + u8 zlp = 1; + int gcnum; + debug("DFU bind\n"); + /* + * Because most host side USB stacks handle CDC Ethernet, that + * standard protocol is _strongly_ preferred for interop purposes. + * (By everyone except Microsoft.) + */ + if (gadget_is_musbhdrc(gadget)) { + /* reduce tx dma overhead by avoiding special cases */ + zlp = 0; + } else if (gadget_is_sa1100(gadget)) { + /* hardware can't write zlps */ + zlp = 0; + } + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + dfu_dev_descriptor.bcdDevice = cpu_to_le16(0x0300 + gcnum); + else { + /* + * can't assume CDC works. don't want to default to + * anything less functional on CDC-capable hardware, + * so we fail in this case. + */ + printf("ERROR : controller not recognised\n"); + error("controller '%s' not recognized", + gadget->name); + return -ENODEV; + } + +#if defined(CONFIG_USBD_VENDORID) && defined(CONFIG_USBD_PRODUCTID_DFU) + dfu_dev_descriptor.idVendor = cpu_to_le16(CONFIG_USBD_VENDORID); + dfu_dev_descriptor.idProduct = cpu_to_le16(CONFIG_USBD_PRODUCTID_DFU); +#endif + + if (bcdDevice) + dfu_dev_descriptor.bcdDevice = cpu_to_le16(bcdDevice); + if (iManufacturer) + strlcpy(manufacturer, iManufacturer, sizeof manufacturer); + if (iProduct) + strlcpy(product_desc, iProduct, sizeof product_desc); + if (iSerialNumber) { + dfu_dev_descriptor.iSerialNumber = DFU_STR_SERIAL, + strlcpy(serial_number, iSerialNumber, sizeof serial_number); + } + + /* all we really need is Ep0 */ + usb_ep_autoconfig_reset(gadget); + + dfu_dev_descriptor.bMaxPacketSize0 = gadget->ep0->maxpacket; + usb_gadget_set_selfpowered(gadget); + + /* preallocate control message data and buffer */ + dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto fail; + dev->req->buf = control_req; + dev->req->complete = dfu_setup_complete; + + /* finish hookup to lower layer ... */ + dev->gadget = gadget; + set_gadget_data(gadget, dev); + gadget->ep0->driver_data = dev; + + return 0; + +fail: + error("%s failed", __func__); + dfu_unbind(gadget); + return -ENOMEM; +} + +int usb_dfu_init(void) +{ + struct dfu_dev *dev = &l_dfudev; + struct usb_gadget *gadget; + unsigned long ts; + unsigned long timeout = USB_CONNECT_TIMEOUT; + int ret; + int i; + + /* set up interface descriptors fro each partition */ + /* this must be done better */ + 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; + uif->iInterface = DFU_STR_ALT(i); + } + + dev->dfu_started = 0; + 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; + + ret = usb_gadget_register_driver(&dfu_driver); + if (ret < 0) { + if (ret != -EBUSY) { /*driver already registered */ + error("USB gadget driver failed to register\n"); + goto fail; + } + } + + gadget = dev->gadget; + usb_gadget_connect(gadget); + + if (getenv("dfu_connect_timeout")) + timeout = simple_strtoul(getenv("dfu_connect_timeout"), + NULL, 10) * CONFIG_SYS_HZ; + ts = get_timer(0); + debug("trying to connect\n"); + while (!dev->dfu_started) { + /* Handle control-c and timeouts */ + if (ctrlc() || (get_timer(ts) > timeout)) { + error("The remote end did not respond in time."); + goto fail; + } + usb_gadget_handle_interrupts(); + } +#ifdef CONFIG_GREEN_LED + green_LED_on(); + ledcount = 100; +#endif +#ifdef CONFIG_RED_LED + red_LED_on(); +#endif + + return 0; +fail: + debug("usb_dfu_init failed\n"); + system_dfu_state = NULL; +#ifdef CONFIG_GREEN_LED + green_LED_off(); + ledcount = 100; +#endif +#ifdef CONFIG_RED_LED + red_LED_off(); +#endif + return -1; +} + +static void dfu_suspend(struct usb_gadget *gadget) +{ + /* Not used */ +} + +static void dfu_resume(struct usb_gadget *gadget) +{ + /* Not used */ +} + +/* we need to bind/unbind and have ep0 */ +static struct usb_gadget_driver dfu_driver = { + .speed = DEVSPEED, + .bind = dfu_bind, + .unbind = dfu_unbind, + .setup = dfu_ep0_handler, + .suspend = dfu_suspend, + .resume = dfu_resume, +}; + + +#endif /* CONFIG_USBD_DFU */ diff --git a/include/usb_dfu.h b/include/usb_dfu.h new file mode 100644 index 0000000..e232a7e --- /dev/null +++ b/include/usb_dfu.h @@ -0,0 +1,128 @@ +#ifndef _DFU_H +#define _DFU_H + +/* USB Device Firmware Update Implementation for u-boot + * (C) 2011 Marcel Janssen , Admesy B.V. + * (C) 2007 by OpenMoko, Inc. + * Author: Harald Welte <lafo...@openmoko.org> + * + * based on: USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 by Harald Welte <hwe...@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 <linux/usb/ch9.h> +#include <usb_dfu_descriptors.h> +#include <config.h> + +#define USB_DFU_SUBCLASS 0x01 + +/* 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 = 0x00, \ + .iInterface = DFU_STR_CONFIG, \ +} + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#ifndef DFU_NUM_ALTERNATES +#define DFU_NUM_ALTERNATES 3 +#endif + +#define STR_MANUFACTURER 0x01 +#define STR_PRODUCT 0x02 +#define STR_SERIAL 0x03 +#define STR_COUNT 0x04 +#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) +/* needed for string list, it works with constants !! */ +#define DFU_STR_ALT1 (STR_COUNT+1) +#define DFU_STR_ALT2 (STR_COUNT+2) +#define DFU_STR_ALT3 (STR_COUNT+3) +#define DFU_STR_ALT4 (STR_COUNT+4) +#define DFU_STR_ALT5 (STR_COUNT+5) +#define DFU_STR_ALT6 (STR_COUNT+6) + +#define CONFIG_DFU_CFG_STR "USB Device Firmware Upgrade" +#define CONFIG_DFU_ALT0_STR "RAM 0x32000000" + +struct _dfu_desc { + struct usb_config_descriptor ucfg; + struct usb_interface_descriptor uif[DFU_NUM_ALTERNATES]; + struct usb_dfu_func_descriptor func_dfu; +}; + +struct dfu_dev { + u8 alternate; + u8 interface; + enum dfu_state dfu_state; + u8 dfu_status; + struct usb_request *req; + const struct usb_ctrlrequest *ctrl; /* last control request */ + + struct usb_gadget *gadget; + + unsigned zlp:1; + u8 config; + struct usb_request *tx_req, *rx_req; + + unsigned int tx_qlen; + unsigned dfu_started:1; + + unsigned long todo; + +}; + +#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 enum dfu_state *system_dfu_state; /* for 3rd parties */ + +int dfu_ep0_handler(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl); +int usb_dfu_init(void); + + +#endif /* _DFU_H */ diff --git a/include/usb_dfu_descriptors.h b/include/usb_dfu_descriptors.h new file mode 100644 index 0000000..ddcafc1 --- /dev/null +++ b/include/usb_dfu_descriptors.h @@ -0,0 +1,100 @@ +#ifndef _USB_DFU_H +#define _USB_DFU_H +/* USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 by Harald Welte <hwe...@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; +/* the following attributes correspond to table 4.1 of the DFU spec */ +#define USB_DFU_CAN_DOWNLOAD (1 << 0) +#define USB_DFU_CAN_UPLOAD (1 << 1) +/* u-boot doesn't need the DFU driver to write to NAND + * the default driver sets this to 0, the original OPENMOKO driver + * had this defined. The dfu-utils don't check for it, which is wrong + * and should be changed +*/ +#define USB_DFU_MANIFEST_TOL (0 << 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 */ -- 1.7.3.4 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot