Signed-off-by: Andrzej Pietrasiewicz <andrze...@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.p...@samsung.com>
---
 drivers/usb/gadget/Makefile |    1 +
 drivers/usb/gadget/dfu.c    |  920 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/dfu.h    |  171 ++++++++
 include/dfu.h               |   28 ++
 include/flash_entity.h      |   39 ++
 5 files changed, 1159 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/dfu.c
 create mode 100644 drivers/usb/gadget/dfu.h
 create mode 100644 include/dfu.h
 create mode 100644 include/flash_entity.h

diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index cd22bbe..4b173e2 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -28,6 +28,7 @@ LIB   := $(obj)libusb_gadget.o
 # new USB gadget layer dependencies
 ifdef CONFIG_USB_GADGET
 COBJS-y += epautoconf.o config.o usbstring.o
+COBJS-$(CONFIG_DFU_GADGET) += dfu.o
 COBJS-$(CONFIG_USB_GADGET_S3C_UDC_OTG) += s3c_udc_otg.o
 endif
 ifdef CONFIG_USB_ETHER
diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c
new file mode 100644
index 0000000..535e194
--- /dev/null
+++ b/drivers/usb/gadget/dfu.c
@@ -0,0 +1,920 @@
+/*
+ * dfu.c -- Device Firmware Update gadget
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrze...@samsung.com>
+ *
+ * Based on gadget zero:
+ * Copyright (C) 2003-2007 David Brownell
+ * All rights reserved.
+ *
+ * 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
+ */
+
+#define VERBOSE_DEBUG
+#define DEBUG
+
+/*
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/device.h>
+*/
+
+#include <common.h>
+#include <asm-generic/errno.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include <flash_entity.h>
+
+#include "gadget_chips.h"
+/* #include "epautoconf.c" */
+/* #include "config.c" */
+/* #include "usbstring.c" */
+
+#include <malloc.h>
+#include "dfu.h"
+
+static struct flash_entity *flash_ents;
+static int num_flash_ents;
+
+static struct usb_device_descriptor device_desc = {
+       .bLength =              sizeof device_desc,
+       .bDescriptorType =      USB_DT_DEVICE,
+       .bcdUSB =               __constant_cpu_to_le16(0x0100),
+       .bDeviceClass =         USB_CLASS_VENDOR_SPEC,
+       .idVendor =             __constant_cpu_to_le16(DRIVER_VENDOR_NUM),
+       .idProduct =            __constant_cpu_to_le16(DRIVER_PRODUCT_NUM),
+       .iManufacturer =        STRING_MANUFACTURER,
+       .iProduct =             STRING_PRODUCT,
+       .iSerialNumber =        STRING_SERIAL,
+       .bNumConfigurations =   1,
+};
+
+static struct usb_config_descriptor dfu_config = {
+       .bLength =              sizeof dfu_config,
+       .bDescriptorType =      USB_DT_CONFIG,
+       /* compute wTotalLength on the fly */
+       .bNumInterfaces =       1,
+       .bConfigurationValue =  DFU_CONFIG_VAL,
+       .iConfiguration =       STRING_DFU_NAME,
+       .bmAttributes =         USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+       .bMaxPower =            1,      /* self-powered */
+};
+
+static struct usb_otg_descriptor otg_descriptor = {
+       .bLength =              sizeof otg_descriptor,
+       .bDescriptorType =      USB_DT_OTG,
+       .bmAttributes =         USB_OTG_SRP,
+};
+
+static const struct dfu_function_descriptor dfu_func = {
+       .bLength =              sizeof dfu_func,
+       .bDescriptorType =      DFU_DT_FUNC,
+       .bmAttributes =         DFU_BIT_WILL_DETACH |
+                               DFU_BIT_MANIFESTATION_TOLERANT |
+                               DFU_BIT_CAN_UPLOAD |
+                               DFU_BIT_CAN_DNLOAD,
+       .wDetachTimeOut =       0,
+       .wTransferSize =        USB_BUFSIZ,
+       .bcdDFUVersion =        __constant_cpu_to_le16(0x0110),
+};
+
+static const struct usb_interface_descriptor dfu_intf_runtime = {
+       .bLength =              sizeof dfu_intf_runtime,
+       .bDescriptorType =      USB_DT_INTERFACE,
+       .bNumEndpoints =        0,
+       .bInterfaceClass =      USB_CLASS_APP_SPEC,
+       .bInterfaceSubClass =   1,
+       .bInterfaceProtocol =   1,
+       .iInterface =           STRING_DFU_NAME,
+};
+
+static const struct usb_descriptor_header *dfu_function_runtime[] = {
+       (struct usb_descriptor_header *) &otg_descriptor,
+       (struct usb_descriptor_header *) &dfu_func,
+       (struct usb_descriptor_header *) &dfu_intf_runtime,
+       NULL,
+};
+
+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_VENDOR_SPEC,
+       .bNumConfigurations =   1,
+};
+
+static char manufacturer[50];
+static const char longname[] = "DFU Gadget";
+/* default serial number takes at least two packets */
+static const char serial[] = "0123456789.0123456789.012345678";
+static const char dfu_name[] = "Device Firmware Upgrade";
+static const char shortname[] = "dfu";
+
+/* static strings, in UTF-8 */
+static struct usb_string strings_runtime[] = {
+       { STRING_MANUFACTURER, manufacturer, },
+       { STRING_PRODUCT, longname, },
+       { STRING_SERIAL, serial, },
+       { STRING_DFU_NAME, dfu_name, },
+       {  }                    /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_runtime = {
+       .language       = 0x0409,       /* en-us */
+       .strings        = strings_runtime,
+};
+
+static struct usb_gadget_strings stringtab_dfu = {
+       .language       = 0x0409,       /* en-us */
+};
+
+static bool is_runtime(struct dfu_dev *dev)
+{
+       return dev->dfu_state == DFU_STATE_appIDLE ||
+               dev->dfu_state == DFU_STATE_appDETACH;
+}
+
+static int config_buf(struct usb_gadget *gadget,
+               u8 *buf, u8 type, unsigned index)
+{
+       int hs = 0;
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       const struct usb_descriptor_header **function;
+       int len;
+
+       if (index > 0)
+               return -EINVAL;
+
+       if (gadget_is_dualspeed(gadget)) {
+               hs = (gadget->speed == USB_SPEED_HIGH);
+               if (type == USB_DT_OTHER_SPEED_CONFIG)
+                       hs = !hs;
+       }
+       if (is_runtime(dev))
+               function = dfu_function_runtime;
+       else
+               function = (const struct usb_descriptor_header **)dev->function;
+
+       /* for now, don't advertise srp-only devices */
+       if (!gadget_is_otg(gadget))
+               function++;
+
+       len = usb_gadget_config_buf(&dfu_config,
+                       buf, USB_BUFSIZ, function);
+       if (len < 0)
+               return len;
+       ((struct usb_config_descriptor *) buf)->bDescriptorType = type;
+       return len;
+}
+
+static void dfu_reset_config(struct dfu_dev *dev)
+{
+       if (dev->config == 0)
+               return;
+
+       DBG(dev, "reset config\n");
+
+       dev->config = 0;
+}
+
+static int dfu_set_config(struct dfu_dev *dev, unsigned number)
+{
+       int result = 0;
+       struct usb_gadget *gadget = dev->gadget;
+       char *speed;
+
+       if (number == dev->config)
+               return 0;
+
+       dfu_reset_config(dev);
+
+       if (DFU_CONFIG_VAL != number) {
+               result = -EINVAL;
+               return result;
+       }
+
+       switch (gadget->speed) {
+       case USB_SPEED_LOW:
+               speed = "low";
+               break;
+       case USB_SPEED_FULL:
+               speed = "full";
+               break;
+       case USB_SPEED_HIGH:
+               speed = "high";
+               break;
+       default:
+               speed = "?";
+               break;
+       }
+
+       dev->config = number;
+       INFO(dev, "%s speed config #%d: %s\n", speed, number, dfu_name);
+       return result;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void empty_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       /* intentionally empty */
+}
+
+static void dnload_request_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct dfu_dev *dev = req->context;
+       struct flash_entity *fe = &flash_ents[dev->altsetting];
+
+       if (dev->not_prepared) {
+               printf("DOWNLOAD %s\n", fe->name);
+               fe->prepare(fe->ctx, FLASH_WRITE);
+               dev->not_prepared = false;
+       }
+
+       if (req->length > 0)
+               fe->write_block(fe->ctx, req->length, req->buf);
+       else {
+               fe->finish(fe->ctx, FLASH_WRITE);
+               dev->not_prepared = true;
+       }
+}
+
+static void handle_getstatus(struct usb_request *req)
+{
+       struct dfu_status *dstat = (struct dfu_status *)req->buf;
+       struct dfu_dev *dev = req->context;
+
+       switch (dev->dfu_state) {
+       case DFU_STATE_dfuDNLOAD_SYNC:
+       case DFU_STATE_dfuDNBUSY:
+               dev->dfu_state = DFU_STATE_dfuDNLOAD_IDLE;
+               break;
+       case DFU_STATE_dfuMANIFEST_SYNC:
+               break;
+       default:
+               break;
+       }
+
+       /* send status response */
+       dstat->bStatus = dev->dfu_status;
+       /* FIXME: set dstat->bwPollTimeout */
+       dstat->bState = dev->dfu_state;
+       dstat->iString = 0;
+}
+
+static void handle_getstate(struct usb_request *req)
+{
+       struct dfu_dev *dev = req->context;
+
+       ((u8 *)req->buf)[0] = dev->dfu_state & 0xff;
+       req->actual = sizeof(u8);
+}
+
+static int handle_upload(struct usb_request *req, u16 len)
+{
+       struct dfu_dev *dev = req->context;
+       struct flash_entity *fe = &flash_ents[dev->altsetting];
+       int n;
+
+       if (dev->not_prepared) {
+               printf("UPLOAD %s\n", fe->name);
+               fe->prepare(fe->ctx, FLASH_READ);
+               dev->not_prepared = false;
+       }
+       n = fe->read_block(fe->ctx, len, req->buf);
+
+       /* no more data to read from this entity */
+       if (n < len) {
+               fe->finish(fe->ctx, FLASH_READ);
+               dev->not_prepared = true;
+       }
+
+       return n;
+}
+
+static int handle_dnload(struct usb_gadget *gadget, u16 len)
+{
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       struct usb_request *req = dev->req;
+
+       if (len == 0)
+               dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
+
+       req->complete = dnload_request_complete;
+
+       return len;
+}
+
+static int
+dfu_handle(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       struct usb_request *req = dev->req;
+       u16 len = le16_to_cpu(ctrl->wLength);
+       int rc = 0;
+
+       switch (dev->dfu_state) {
+       case DFU_STATE_appIDLE:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               case USB_REQ_DFU_DETACH:
+                       dev->dfu_state = DFU_STATE_appDETACH;
+                       dev->dfu_state = DFU_STATE_dfuIDLE;
+                       return RET_ZLP;
+               default:
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_appDETACH:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_appIDLE;
+                       return RET_STALL;
+               }
+               /* FIXME: implement timer to return to appIDLE */
+               break;
+       case DFU_STATE_dfuIDLE:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_DNLOAD:
+                       if (len == 0) {
+                               dev->dfu_state = DFU_STATE_dfuERROR;
+                               return RET_STALL;
+                       }
+                       dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+                       return handle_dnload(gadget, len);
+               case USB_REQ_DFU_UPLOAD:
+                       dev->dfu_state = DFU_STATE_dfuUPLOAD_IDLE;
+                       return handle_upload(req, len);
+               case USB_REQ_DFU_ABORT:
+                       /* no zlp? */
+                       return RET_ZLP;
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       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;
+                       dev->dfu_state = DFU_STATE_appIDLE;
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuDNLOAD_SYNC:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+                       /* FIXME: state transition depending
+                        * on block completeness */
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuDNBUSY:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       /* FIXME: only accept getstatus if bwPollTimeout
+                        * has elapsed */
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuDNLOAD_IDLE:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_DNLOAD:
+                       dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+                       return handle_dnload(gadget, len);
+               case USB_REQ_DFU_ABORT:
+                       dev->dfu_state = DFU_STATE_dfuIDLE;
+                       return RET_ZLP;
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuMANIFEST_SYNC:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       /* We're MainfestationTolerant */
+                       dev->dfu_state = DFU_STATE_dfuIDLE;
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuMANIFEST:
+               /* we should never go here */
+               dev->dfu_state = DFU_STATE_dfuERROR;
+               return RET_STALL;
+       case DFU_STATE_dfuMANIFEST_WAIT_RST:
+               /* we should never go here */
+               break;
+       case DFU_STATE_dfuUPLOAD_IDLE:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_UPLOAD:
+                       /* state transition if less data then requested */
+                       rc = handle_upload(req, len);
+                       if (rc >= 0 && rc < len)
+                               dev->dfu_state = DFU_STATE_dfuIDLE;
+                       return rc;
+               case USB_REQ_DFU_ABORT:
+                       dev->dfu_state = DFU_STATE_dfuIDLE;
+                       /* no zlp? */
+                       return RET_ZLP;
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       case DFU_STATE_dfuERROR:
+               switch (ctrl->bRequest) {
+               case USB_REQ_DFU_GETSTATUS:
+                       handle_getstatus(req);
+                       return RET_STAT_LEN;
+               case USB_REQ_DFU_GETSTATE:
+                       handle_getstate(req);
+                       break;
+               case USB_REQ_DFU_CLRSTATUS:
+                       dev->dfu_state = DFU_STATE_dfuIDLE;
+                       dev->dfu_status = DFU_STATUS_OK;
+                       /* no zlp? */
+                       return RET_ZLP;
+               default:
+                       dev->dfu_state = DFU_STATE_dfuERROR;
+                       return RET_STALL;
+               }
+               break;
+       default:
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+dfu_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       struct usb_request *req = dev->req;
+       int value = -EOPNOTSUPP;
+       u16 w_index = le16_to_cpu(ctrl->wIndex);
+       u16 w_value = le16_to_cpu(ctrl->wValue);
+       u16 w_length = le16_to_cpu(ctrl->wLength);
+
+       req->zero = 0;
+       req->complete = empty_complete;
+       if (!(ctrl->bRequestType & USB_TYPE_CLASS))
+               switch (ctrl->bRequest) {
+
+               case USB_REQ_GET_DESCRIPTOR:
+                       if (ctrl->bRequestType != USB_DIR_IN)
+                               goto unknown;
+                       switch (w_value >> 8) {
+
+                       case USB_DT_DEVICE:
+                               value = min(w_length, (u16) sizeof device_desc);
+                               memcpy(req->buf, &device_desc, value);
+                               break;
+                       case USB_DT_DEVICE_QUALIFIER:
+                               if (!gadget_is_dualspeed(gadget))
+                                       break;
+                               value = min(w_length,
+                                           (u16) sizeof dev_qualifier);
+                               memcpy(req->buf, &dev_qualifier, value);
+                               break;
+
+                       case USB_DT_OTHER_SPEED_CONFIG:
+                               if (!gadget_is_dualspeed(gadget))
+                                       break;
+                               /* FALLTHROUGH */
+                       case USB_DT_CONFIG:
+                               value = config_buf(gadget, req->buf,
+                                               w_value >> 8,
+                                               w_value & 0xff);
+                               if (value >= 0)
+                                       value = min_t(w_length, (u16) value);
+                               break;
+
+                       case USB_DT_STRING:
+                               /* wIndex == language code. */
+                               value = usb_gadget_get_string(
+                                       is_runtime(dev) ? &stringtab_runtime :
+                                       &stringtab_dfu,
+                                       w_value & 0xff, req->buf);
+                               if (value >= 0)
+                                       value = min_t(w_length, (u16) value);
+                               break;
+                       case DFU_DT_FUNC:
+                               value = min(w_length, (u16) sizeof dfu_func);
+                               memcpy(req->buf, &dfu_func, value);
+                               break;
+                       }
+                       break;
+
+               case USB_REQ_SET_CONFIGURATION:
+                       if (ctrl->bRequestType != 0)
+                               goto unknown;
+                       if (gadget->a_hnp_support)
+                               DBG(dev, "HNP available\n");
+                       else if (gadget->a_alt_hnp_support)
+                               DBG(dev, "HNP needs a different root port\n");
+                       else
+                               VDBG(dev, "HNP inactive\n");
+                       spin_lock(&dev->lock);
+                       value = dfu_set_config(dev, w_value);
+                       spin_unlock(&dev->lock);
+                       break;
+               case USB_REQ_GET_CONFIGURATION:
+                       if (ctrl->bRequestType != USB_DIR_IN)
+                               goto unknown;
+                       *(u8 *)req->buf = dev->config;
+                       value = min(w_length, (u16) 1);
+                       break;
+
+               /* until we add altsetting support, or other interfaces,
+                * only 0/0 are possible.  pxa2xx only supports 0/0 (poorly)
+                * and already killed pending endpoint I/O.
+                */
+               case USB_REQ_SET_INTERFACE:
+                       if (ctrl->bRequestType != USB_RECIP_INTERFACE)
+                               goto unknown;
+                       spin_lock(&dev->lock);
+                       if (dev->config && w_index == 0) {
+                               u8              config = dev->config;
+
+                               /* resets interface configuration, forgets about
+                                * previous transaction state (queued bufs, etc)
+                                * and re-inits endpoint state (toggle etc)
+                                * no response queued, just zero
+                                * status == success.
+                                * if we had more than one interface we couldn't
+                                * use this "reset the config" shortcut.
+                                */
+                               dfu_reset_config(dev);
+                               dfu_set_config(dev, config);
+                               dev->altsetting = w_value;
+                               value = 0;
+                       }
+                       spin_unlock(&dev->lock);
+                       break;
+               case USB_REQ_GET_INTERFACE:
+                       if (ctrl->bRequestType !=
+                           (USB_DIR_IN|USB_RECIP_INTERFACE))
+                               goto unknown;
+                       if (!dev->config)
+                               break;
+                       if (w_index != 0) {
+                               value = -EDOM;
+                               break;
+                       }
+                       *(u8 *)req->buf = 0;
+                       value = min(w_length, (u16) 1);
+                       break;
+
+               default:
+unknown:
+                       VDBG(dev,
+                               "unknown control req%02x.%02x v%04x i%04x 
l%d\n",
+                               ctrl->bRequestType, ctrl->bRequest,
+                               w_value, w_index, w_length);
+               }
+       else
+               value = dfu_handle(gadget, ctrl);
+
+       /* respond with data transfer before status phase? */
+       if (value >= 0) {
+               req->length = value;
+               req->zero = value < w_length;
+               value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
+               if (value < 0) {
+                       DBG(dev, "ep_queue --> %d\n", value);
+                       req->status = 0;
+               }
+       }
+
+       /* device either stalls (value < 0) or reports success */
+       return value;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void dfu_disconnect(struct usb_gadget *gadget)
+{
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       dfu_reset_config(dev);
+       spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static int dfu_prepare_function(struct dfu_dev *dev, int n)
+{
+       struct usb_interface_descriptor *d;
+       int i = 0;
+
+       dev->function = kzalloc((ALTSETTING_BASE + n + 1) *
+                               sizeof(struct usb_descriptor_header *),
+                               GFP_KERNEL);
+       if (!dev->function)
+               goto enomem;
+
+       dev->function[0] = (struct usb_descriptor_header *)&otg_descriptor;
+       dev->function[1] = (struct usb_descriptor_header *)&dfu_func;
+
+       for (i = 0; i < n; ++i) {
+               d = kzalloc(sizeof(*d), GFP_KERNEL);
+               if (!d)
+                       goto enomem;
+
+               d->bLength =            sizeof(*d);
+               d->bDescriptorType =    USB_DT_INTERFACE;
+               d->bAlternateSetting =  i;
+               d->bNumEndpoints =      0;
+               d->bInterfaceClass =    USB_CLASS_APP_SPEC;
+               d->bInterfaceSubClass = 1;
+               d->bInterfaceProtocol = 2;
+               d->iInterface =         DFU_STR_BASE + i;
+
+               dev->function[ALTSETTING_BASE + i] =
+                       (struct usb_descriptor_header *)d;
+       }
+       dev->function[ALTSETTING_BASE + i] = NULL;
+
+       return 1;
+
+enomem:
+       while (i) {
+               kfree(dev->function[--i + ALTSETTING_BASE]);
+               dev->function[i + ALTSETTING_BASE] = NULL;
+       }
+       kfree(dev->function);
+
+       return 0;
+}
+
+static int
+dfu_prepare_strings(struct dfu_dev *dev, struct flash_entity *f, int n)
+{
+       int i = 0;
+
+       dev->strings = kzalloc((STRING_ALTSETTING_BASE + n + 1) *
+                               sizeof(struct usb_string),
+                               GFP_KERNEL);
+       if (!dev->strings)
+               goto enomem;
+
+       dev->strings[0].id = STRING_MANUFACTURER;
+       dev->strings[0].s = manufacturer;
+       dev->strings[1].id = STRING_PRODUCT;
+       dev->strings[1].s = longname;
+       dev->strings[2].id = STRING_SERIAL;
+       dev->strings[2].s = serial;
+       dev->strings[3].id = STRING_DFU_NAME;
+       dev->strings[3].s = dfu_name;
+
+       for (i = 0; i < n; ++i) {
+               char *s;
+
+               dev->strings[STRING_ALTSETTING_BASE + i].id = DFU_STR_BASE + i;
+               s = kzalloc(strlen(f[i].name) + 1, GFP_KERNEL);
+               if (!s)
+                       goto enomem;
+
+               strcpy(s, f[i].name);
+               dev->strings[STRING_ALTSETTING_BASE + i].s = s;
+       }
+       dev->strings[STRING_ALTSETTING_BASE + i].id = 0;
+       dev->strings[STRING_ALTSETTING_BASE + i].s = NULL;
+
+       return 1;
+
+enomem:
+       while (i) {
+               kfree((void *)dev->strings[--i + STRING_ALTSETTING_BASE].s);
+               dev->strings[i + STRING_ALTSETTING_BASE].s = NULL;
+       }
+       kfree(dev->strings);
+
+       return 0;
+}
+
+static void dfu_unbind(struct usb_gadget *gadget)
+{
+       struct dfu_dev *dev = get_gadget_data(gadget);
+       int i;
+
+       DBG(dev, "unbind\n");
+
+       if (dev->strings) {
+               i = num_flash_ents;
+               while (i) {
+                       kfree((void *)
+                             dev->strings[--i + STRING_ALTSETTING_BASE].s);
+                       dev->strings[i + STRING_ALTSETTING_BASE].s = NULL;
+               }
+               kfree(dev->strings);
+       }
+       if (dev->function) {
+               i = num_flash_ents;
+               while (i) {
+                       kfree(dev->function[--i + ALTSETTING_BASE]);
+                       dev->function[i + ALTSETTING_BASE] = NULL;
+               }
+               kfree(dev->function);
+       }
+       /* we've already been disconnected ... no i/o is active */
+       if (dev->req) {
+               dev->req->length = USB_BUFSIZ;
+               kfree(dev->req->buf);
+               usb_ep_free_request(gadget->ep0, dev->req);
+       }
+       kfree(dev);
+       set_gadget_data(gadget, NULL);
+}
+
+static int __init dfu_bind(struct usb_gadget *gadget)
+{
+       struct dfu_dev *dev;
+       int gcnum;
+
+       usb_ep_autoconfig_reset(gadget);
+
+       gcnum = usb_gadget_controller_number(gadget);
+       if (gcnum >= 0)
+               device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
+       else {
+               pr_warning("%s: controller '%s' not recognized\n",
+                       shortname, gadget->name);
+               device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+       }
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+       spin_lock_init(&dev->lock);
+       dev->gadget = gadget;
+       set_gadget_data(gadget, dev);
+
+       dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
+       if (!dev->req)
+               goto enomem;
+       dev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
+       if (!dev->req->buf)
+               goto enomem;
+
+       dev->req->complete = empty_complete;
+
+       device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
+
+       if (gadget_is_dualspeed(gadget))
+               dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0;
+
+       if (gadget_is_otg(gadget)) {
+               otg_descriptor.bmAttributes |= USB_OTG_HNP,
+               dfu_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+       }
+
+       usb_gadget_set_selfpowered(gadget);
+
+       dev->dfu_state = DFU_STATE_appIDLE;
+       dev->dfu_status = DFU_STATUS_OK;
+       dev->not_prepared = true;
+
+       if (!dfu_prepare_function(dev, num_flash_ents))
+               goto enomem;
+
+       if (!dfu_prepare_strings(dev, flash_ents, num_flash_ents))
+               goto enomem;
+       stringtab_dfu.strings = dev->strings;
+
+       gadget->ep0->driver_data = dev;
+       dev->req->context = dev;
+
+       INFO(dev, "%s, version: " DRIVER_VERSION "\n", longname);
+
+       /* snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
+               init_utsname()->sysname, init_utsname()->release,
+               gadget->name); */
+
+       return 0;
+
+enomem:
+       dfu_unbind(gadget);
+       return -ENOMEM;
+}
+
+static void dfu_suspend(struct usb_gadget *gadget)
+{
+       if (gadget->speed == USB_SPEED_UNKNOWN)
+               return;
+
+       DBG(dev, "suspend\n");
+}
+
+static void dfu_resume(struct usb_gadget *gadget)
+{
+       DBG(dev, "resume\n");
+}
+
+static struct usb_gadget_driver dfu_driver = {
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+       .speed          = USB_SPEED_HIGH,
+#else
+       .speed          = USB_SPEED_FULL,
+#endif
+       /*.function     = (char *) longname,*/
+       .bind           = dfu_bind,
+       .unbind         = __exit_p(dfu_unbind),
+
+       .setup          = dfu_setup,
+       .disconnect     = dfu_disconnect,
+
+       .suspend        = dfu_suspend,
+       .resume         = dfu_resume,
+
+       /*
+       .driver         = {
+               .name           = (char *) shortname,
+               .owner          = THIS_MODULE,
+       },*/
+};
+
+void register_flash_entities(struct flash_entity *flents, int n)
+{
+       flash_ents = flents;
+       num_flash_ents = n;
+}
+
+int __init dfu_init(void)
+{
+       return usb_gadget_register_driver(&dfu_driver);
+}
+module_init(dfu_init);
+
+void __exit dfu_cleanup(void)
+{
+       usb_gadget_unregister_driver(&dfu_driver);
+}
+module_exit(cleanup);
+
diff --git a/drivers/usb/gadget/dfu.h b/drivers/usb/gadget/dfu.h
new file mode 100644
index 0000000..5559273
--- /dev/null
+++ b/drivers/usb/gadget/dfu.h
@@ -0,0 +1,171 @@
+/*
+ * dfu.h -- Device Firmware Update gadget
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrze...@samsung.com>
+ *
+ * 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
+ */
+
+#ifndef DFU_H_
+#define DFU_H_
+
+#include <linux/compiler.h>
+
+/*
+ * Linux kernel compatibility layer
+ */
+#define GFP_ATOMIC                             ((gfp_t) 0)
+#define GFP_KERNEL                             ((gfp_t) 0)
+#define true                                   1
+#define false                                  0
+#define dev_dbg(...)                           do {} while (0)
+#define dev_vdbg(...)                          do {} while (0)
+#define dev_err(...)                           do {} while (0)
+#define dev_warn(...)                          do {} while (0)
+#define dev_info(...)                          do {} while (0)
+#define pr_warning(...)                                do {} while (0)
+#define spin_lock_init(lock)                   do {} while (0)
+#define spin_lock(lock)                                do {} while (0)
+#define spin_unlock(lock)                      do {} while (0)
+#define spin_lock_irqsave(lock,flags)          do {flags = 1;} while (0)
+#define spin_unlock_irqrestore(lock,flags)     do {flags = 0;} while (0)
+#define kmalloc(x,y)                           malloc(x)
+#define kfree(x)                               free(x)
+#define kzalloc(size,flags)                    calloc((size), 1)
+#define __init
+#define __exit
+#define __exit_p(x)                            x
+#define module_init(...)
+#define module_exit(...)
+#define min_t                                  min
+#define spinlock_t                             int
+#define bool                                   int
+/*
+ * end compatibility layer
+ */
+
+#define DBG(d, fmt, args...) \
+       dev_dbg(&(d)->gadget->dev , fmt , ## args)
+#define VDBG(d, fmt, args...) \
+       dev_vdbg(&(d)->gadget->dev , fmt , ## args)
+#define ERROR(d, fmt, args...) \
+       dev_err(&(d)->gadget->dev , fmt , ## args)
+#define WARN(d, fmt, args...) \
+       dev_warn(&(d)->gadget->dev , fmt , ## args)
+#define INFO(d, fmt, args...) \
+       dev_info(&(d)->gadget->dev , fmt , ## args)
+
+#define DRIVER_VERSION                 "Msciwoj"
+
+/* Thanks to NetChip Technologies for donating this product ID.  */
+#define DRIVER_VENDOR_NUM              0x0525          /* NetChip */
+#define DRIVER_PRODUCT_NUM             0xffff          /* DFU */
+
+#define STRING_MANUFACTURER            0
+#define STRING_PRODUCT                 1
+#define STRING_SERIAL                  2
+#define STRING_DFU_NAME                        49
+#define DFU_STR_BASE                   50
+
+#define        DFU_CONFIG_VAL                  1
+#define DFU_DT_FUNC                    0x21
+
+#define DFU_BIT_WILL_DETACH            (0x1 << 3)
+#define DFU_BIT_MANIFESTATION_TOLERANT (0x1 << 2)
+#define DFU_BIT_CAN_UPLOAD             (0x1 << 1)
+#define DFU_BIT_CAN_DNLOAD             0x1
+
+/* big enough to hold our biggest descriptor */
+#define USB_BUFSIZ                     4096
+
+#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
+
+#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
+
+#define RET_STALL                      -1
+#define RET_ZLP                                0
+#define RET_STAT_LEN                   6
+
+#define ALTSETTING_BASE                        2
+#define STRING_ALTSETTING_BASE         4
+
+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,
+};
+
+struct dfu_status {
+       __u8                            bStatus;
+       __u8                            bwPollTimeout[3];
+       __u8                            bState;
+       __u8                            iString;
+} __packed;
+
+struct dfu_function_descriptor {
+       __u8                            bLength;
+       __u8                            bDescriptorType;
+       __u8                            bmAttributes;
+       __le16                          wDetachTimeOut;
+       __le16                          wTransferSize;
+       __le16                          bcdDFUVersion;
+} __packed;
+
+struct dfu_dev {
+       spinlock_t                      lock;
+       struct usb_gadget               *gadget;
+       struct usb_request              *req;   /* for control responses */
+
+       /* when configured, we have one config */
+       u8                              config;
+       u8                              altsetting;
+       enum dfu_state                  dfu_state;
+       unsigned int                    dfu_status;
+       struct usb_descriptor_header    **function;
+       struct usb_string               *strings;
+       bool                            not_prepared;
+};
+
+#endif /* DFU_H_ */
diff --git a/include/dfu.h b/include/dfu.h
new file mode 100644
index 0000000..21b89f2
--- /dev/null
+++ b/include/dfu.h
@@ -0,0 +1,28 @@
+/*
+ * dfu.h - Device Firmware Upgrade
+ *
+ * copyright (c) 2011 samsung electronics
+ * author: andrzej pietrasiewicz <andrze...@samsung.com>
+ *
+ * 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
+ */
+
+#ifndef __DFU_H__
+#define __DFU_H__
+
+extern int dfu_init(void);
+extern int dfu_cleanup(void);
+
+#endif
diff --git a/include/flash_entity.h b/include/flash_entity.h
new file mode 100644
index 0000000..daa90ee
--- /dev/null
+++ b/include/flash_entity.h
@@ -0,0 +1,39 @@
+/*
+ * flash_entity.h - flashable area description
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrze...@samsung.com>
+ *
+ * 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
+ */
+
+#ifndef FLASH_ENTITY_H_
+#define FLASH_ENTITY_H_
+
+#define FLASH_READ                     0
+#define FLASH_WRITE                    1
+
+struct flash_entity {
+       char                            *name;
+       void                            *ctx;
+       int (*prepare)(void *ctx, u8 mode);
+       int (*read_block)(void *ctx, unsigned int n, void *buf);
+       int (*write_block)(void *ctx, unsigned int n, void *buf);
+       int (*finish)(void *ctx, u8 mode);
+};
+
+void register_flash_entities(struct flash_entity *flents, int n);
+
+#endif /* FLASH_ENTITY_H_ */
-- 
1.7.0.4

_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
http://lists.denx.de/mailman/listinfo/u-boot

Reply via email to