[U-Boot] [PATCH v2 2/4] USB DFU driver added
Marcel Janssen
korgull at home.nl
Sun Feb 13 22:52:25 CET 2011
From: Marcel <korgull at 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 <korgull at 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 at 0x00200000 root
+mtdparts add nand0 0xEE00000 at 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 <laforge at openmoko.org>
+ *
+ * based on existing SAM7DFU code from OpenPCD:
+ * (C) Copyright 2006 by Harald Welte <hwelte at 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 <laforge at openmoko.org>
+ *
+ * based on: USB Device Firmware Update Implementation for OpenPCD
+ * (C) 2006 by Harald Welte <hwelte at hmw-consulting.de>
+ *
+ * This ought to be compliant to the USB DFU Spec 1.0 as available from
+ * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <asm/types.h>
+#include <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 <hwelte at hmw-consulting.de>
+ *
+ * Protocol definitions for USB DFU
+ *
+ * This ought to be compliant to the USB DFU Spec 1.0 as available from
+ * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/types.h>
+
+#define USB_DT_DFU 0x21
+
+struct usb_dfu_func_descriptor {
+ u_int8_t bLength;
+ u_int8_t bDescriptorType;
+ u_int8_t bmAttributes;
+/* 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
More information about the U-Boot
mailing list