[PATCH] usb: gadget: DFU - initial release

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a Device Firmware Upgrade (DFU) implementation as a kernel module.
For details please see Documentation/usb/dfu.txt.

TODO: This driver needs some refactoring to fit it into a composite gadget.

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
---
 Documentation/usb/dfu.txt   |  155 +++++
 drivers/usb/gadget/Kconfig  |    8 +
 drivers/usb/gadget/Makefile |    2 +
 drivers/usb/gadget/dfu.c    | 1595 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/dfu.h     |   92 +++
 5 files changed, 1852 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/usb/dfu.txt
 create mode 100644 drivers/usb/gadget/dfu.c
 create mode 100644 include/linux/usb/dfu.h

diff --git a/Documentation/usb/dfu.txt b/Documentation/usb/dfu.txt
new file mode 100644
index 0000000..ab08ee3
--- /dev/null
+++ b/Documentation/usb/dfu.txt
@@ -0,0 +1,155 @@
+
+                      Device Firmware Upgrade
+                      =======================
+
+                            26 July 2011
+
+              Copyright (C) 2011 Samsung Electronics
+        author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
+
+This note accompanies the Device Firmware Upgrade kernel module.
+
+0. Disclaimer
+-------------
+
+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
+
+1. Overview
+-----------
+
+Device Firmware Upgrade (DFU) is an extension to the USB specification.
+As of the time of this writing it is documented at
+
+http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf
+
+The aim of DFU is to allow transferring data to/from selected areas of
+interest within a compliant device. In the DFU nomenclature the host->device
+direction is called download and the device->host direction is called
+upload. The areas are defined by the compliant device. In the DFU
+nomenclature the area of interest within the device is called an altsetting.
+The device is connected to the host through USB. On the host side compliant
+software must be used to initiate the data transfer. Example piece of such
+software is dfu-util package from the OpenMoko project:
+
+http://wiki.openmoko.org/wiki/Dfu-util.
+
+Please refer to dfu-util documentation for details.
+The below ASCII-art presents a connection between the host and the device.
+
+      +-----------------+    <--- DFU --->    +-------------------------+
+      |  e.g. dfu-util  |    <=== USB ===>    |          / altsetting 0 |
+      |          host   +---------------------+   device - altsetting 1 |
+      |   file /        |                     |          \     ...      |
+      +-----------------+                     +-------------------------+
+
+2. Basic usage, host side
+-------------------------
+
+The DFU implementation on the device side remains in one of the two modes:
+the Run-Time mode and the DFU mode. The USB descriptor sets exported by
+the device depends on which mode is in force. While in DFU mode the device
+exports the descriptors corresponding to each available altsetting. An
+example dfu-util session looks similar to this:
+
+# dfu-util -l
+dfu-util - (C) 2007-2008 by OpenMoko Inc.
+This program is Free Software and has ABSOLUTELY NO WARRANTY
+
+Found DFU: [0x0525:0xffff] devnum=46, cfg=0, intf=0, alt=0, name="bootldr"
+Found DFU: [0x0525:0xffff] devnum=46, cfg=0, intf=0, alt=1, name="kernel"
+
+In the above example there are two altsettings, altsetting 0 with a
+human-readable name "bootldr" and altsetting 1 with a human-readable name
+"kernel"
+
+To initiate data transfer the user at the host side must specify the
+altsetting to/from which the data transfer is to be performed. In case of
+the dfu-util it is specified with a "-a" option followed by either a number
+or a human-readable name. The user also needs to specify the direction of
+the data transfer and the file on the host from/to which data are to be
+transfered. An example download command line looks similar to this:
+
+# dfu-util -D vmlinux -a kernel
+
+In the above command line the contents of the file vmlinux is to be
+downloaded into the altsetting named kernel.
+
+An example upload command line looks similar to this:
+
+# dfu-util -U vmlinux -a kernel
+
+In the above command line the contents of the altsetting named kernel is to
+be uploaded into the file vmlinux.
+
+3. Kernel module usage, device side
+-----------------------------------
+
+The DFU specifiaction is inspired by the need to perform product
+enhancements and patches to devices which are already deployed in the field.
+In an example scenario the device's bootloader can implement DFU and define
+the altsettings as some areas of the flash memory. The user boots the device
+in a special way and then using a standard tool performs the data transfer,
+the data being written to the respective flash memory area.
+
+The DFU kernel module operates in a slightly different way. The altsettings
+are associated with entities, which can be either regular files or e.g.
+block device special files.
+
+The module accepts a "nents" parameter which specifies how many entities are
+to be used, 1 by default. For each entity there is a corresponding entry in
+sysfs, e.g.:
+
+/sys/devices/platform/s3c-hsotg/gadget/dfu/alt0
+
+s3c-hsotg corresponds to the UDC (OTG) and can be different on different
+devices. Initially all entities are in "<unconfigured>" state: reading from
+them (upload) gives zero-length data and data written to them (download) are
+silently ignored. Each entry has an attribute "fname" to associate the
+entity with a file. This command line:
+
+echo -n /dfu.bin > /sys/devices/platform/s3c-hsotg/gadget/dfu/alt0/fname
+
+associates the altsetting 0 with the file /dfu.bin. Absolute pathnames need
+to be used.
+
+On the other hand, the command line:
+
+echo -n /dev/mmcblk0p1 > /sys/devices/platform/s3c-hsotg/gadget/dfu/alt0/fname
+
+associates the altsetting 0 with partition 1 on mmcblk0 device.
+
+Before association of an altsetting and a file, the altsetting
+human-readablename equals to "<unconfigured>". After the altsetting has been
+associated with the file, the altsetting's human-readable name becomes the
+absolute pathname of the file, in the above example it is "/dfu.bin" and
+/dev/mmcblk0p1, respectively. Please note that using the altsetting number is
+also a valid way of specifying the altsetting by the host side.
+
+4. Kernel module high level design
+----------------------------------
+
+The kernel module implements a USB gadget. All DFU-specific protocol
+handling is delegated to a dedicated function. The USB and DFU events are,
+in general, handled in interrupt context, except operations which
+potentially take a long time (disk access) as well as configuration change
+requests, which are delegated to a separate kernel thread. The thread is
+named "dfu-stor" and can be seen by running this command:
+
+# ps ax | grep dfu
+ 2523 ?        S      0:00 [dfu-stor]
+
+Killing the thread causes the driver to unbind. Conversely, unloading the
+driver module causes the thread to terminate.
+
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 694e96c..dbb5a0d 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -1049,6 +1049,14 @@ config USB_G_HID
 	  Say "y" to link the driver statically, or "m" to build a
 	  dynamically linked module called "g_hid".
 
+config USB_DFU
+	tristate "Device Firmware Upgrade (DFU) gadget"
+	help
+	  This gadget driver provides DFU capability.
+
+	  Say "y" to link the driver statically, or "m" to build a
+	  dynamically linked module called "g_dfu".
+
 config USB_G_DBGP
 	tristate "EHCI Debug Device Gadget"
 	help
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 74f3ee9..8e228cf 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -50,6 +50,7 @@ g_dbgp-y			:= dbgp.o
 g_nokia-y			:= nokia.o
 g_webcam-y			:= webcam.o
 g_ncm-y				:= ncm.o
+g_dfu-y				:= dfu.o
 
 obj-$(CONFIG_USB_ZERO)		+= g_zero.o
 obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
@@ -68,3 +69,4 @@ obj-$(CONFIG_USB_G_MULTI)	+= g_multi.o
 obj-$(CONFIG_USB_G_NOKIA)	+= g_nokia.o
 obj-$(CONFIG_USB_G_WEBCAM)	+= g_webcam.o
 obj-$(CONFIG_USB_G_NCM)		+= g_ncm.o
+obj-$(CONFIG_USB_DFU)		+= g_dfu.o
diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c
new file mode 100644
index 0000000..eb6733e
--- /dev/null
+++ b/drivers/usb/gadget/dfu.c
@@ -0,0 +1,1595 @@
+/*
+ * dfu.c -- Device Firmware Upgrade gadget
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
+ *
+ * Based on gadget zero:
+ * Copyright (C) 2003-2007 David Brownell
+ *
+ * Contains modified parts from OpenMoko u-boot: drivers/usb/usbdfu.c
+ * (C) 2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@xxxxxxxxxxxx>
+ *
+ * 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 <linux/fs.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/dfu.h>
+
+#include "gadget_chips.h"
+#include "epautoconf.c"
+#include "config.c"
+#include "usbstring.c"
+
+#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 INFO(d, fmt, args...) \
+	dev_info(&(d)->gadget->dev , fmt , ## args)
+
+#define DRIVER_VERSION			"Unieslaw"
+
+/* 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 USB_BUFSIZ			4096
+#define FILE_BUF_LEN			512
+
+#define RET_STALL			-1
+#define RET_ZLP				0
+#define RET_STAT_LEN			6
+
+#define ALTSETTING_BASE			2
+#define STRING_ALTSETTING_BASE		4
+
+#define DFU_READ			0
+#define DFU_WRITE			1
+
+struct dfu_dev {
+	spinlock_t			lock;
+	struct usb_gadget		*gadget;
+	struct dfu_entity		*entities;
+	struct dfu_entity_ctx		*contexts;
+	struct device			sysfs_dfu;
+	bool				sysfs_dfu_reg;
+	struct kref			ref;
+	struct usb_request		*req;	/* for control responses */
+
+	/* USB configuration related */
+	u8				config;
+	u8				altsetting;
+	enum dfu_state			dfu_state;
+	unsigned int			dfu_status;
+	struct usb_descriptor_header	**function;
+	struct usb_string		*strings;
+
+	/* worker thread handling */
+	struct task_struct		*thread;
+	struct completion		thread_completion;
+	unsigned long			atomic_flags;
+#define REGISTERED			0
+#define TERMINATED			1
+	struct usb_request		*thr_rq; /* device->host transfer */
+	atomic_t			dnload_count;
+	struct completion		dnload_completion;
+	u16				dnload_len;
+	atomic_t			upload_count;
+	struct completion		upload_completion;
+	u16				upload_len;
+
+	/* interface/config setting handling */
+	atomic_t			cfg_chng_pending;
+	u8				new_config;
+	u8				new_altsetting;
+	u16				old_w_length;
+	bool				do_reset;
+
+	/* autoresume timer */
+	struct timer_list		resume;
+};
+
+struct dfu_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);
+};
+
+static struct usb_gadget_driver dfu_driver;
+static struct dfu_dev *the_dfu;
+
+static unsigned int num_entities = 1;
+module_param_named(nents, num_entities, uint, S_IRUGO);
+MODULE_PARM_DESC(nents, "number of entities");
+
+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 char file_buf[FILE_BUF_LEN];
+
+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 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 */
+};
+
+/*
+ * Prototypes to improve code readability
+ */
+static void dfu_reset_config(struct dfu_dev *dev);
+static int dfu_set_config(struct dfu_dev *dev, unsigned number);
+static void empty_complete(struct usb_ep *ep, struct usb_request *req);
+
+/*
+ *
+ * DFU storage backend
+ *
+ */
+typedef int (*rw_op)(struct file *fp, void *buf, unsigned int len,
+		     unsigned long from);
+
+struct dfu_entity_ctx {
+	loff_t			length; /* size of the entity */
+	loff_t			num_done; /* total bytes handled */
+	u8			*buf; /* buffer for one chunk */
+	unsigned int		buf_len; /* one chunk length */
+	unsigned int		buffered; /* # available bytes in buf */
+	unsigned int		num_read; /* bytes read into buf */
+	int			full:1; /* file cannot grow anymore */
+	int			prepared:1; /* good for operation */
+	rw_op			read; /* chunk read op for this medium */
+	rw_op			write; /* chunk write op for this medium */
+	struct dfu_entity	*this_entity; /* the containing entity */
+	struct device		dev; /* sysfs entry */
+	int			registered:1; /* registration flag */
+	int			sysfs_attr:1; /* attribute creation flag */
+	struct file		*fp; /* associated file */
+	int			ro:1; /* file not writeable */
+	int			wo:1; /* file not readable */
+	struct mutex		file_mutex; /* serialize r/w and file change */
+};
+
+static inline void reset_context(void *ctx)
+{
+	struct dfu_entity_ctx *ct = ctx;
+	ct->buffered = 0;
+	ct->num_done = 0;
+	ct->num_read = 0;
+	ct->full = false;
+	ct->prepared = false;
+}
+
+static inline bool ctx_prepared(void *ctx)
+{
+	struct dfu_entity_ctx *ct = ctx;
+
+	return ct->prepared;
+}
+
+static inline struct inode *ctx_inode(void *ctx)
+{
+	struct dfu_entity_ctx *ct = ctx;
+
+	return ct->fp ?
+		ct->fp->f_path.dentry ? ct->fp->f_path.dentry->d_inode : NULL
+		: NULL;
+}
+
+static int prepare(void *ctx, u8 mode)
+{
+	struct dfu_entity_ctx *ct = ctx;
+
+	reset_context(ct);
+
+	ct->prepared = true;
+
+	if (!ct->fp && ct->this_entity->name) {
+		struct inode *inode = NULL;
+
+		ct->fp = filp_open(ct->this_entity->name,
+				     O_RDWR | O_LARGEFILE, 0);
+		if (EROFS == PTR_ERR(ct->fp)) {
+			ct->ro = 1;
+			ct->fp = filp_open(ct->this_entity->name,
+					     O_RDONLY | O_LARGEFILE, 0);
+		}
+		if (IS_ERR(ct->fp))
+			goto file_err;
+
+		if (!(ct->fp->f_mode & FMODE_WRITE))
+			ct->ro = 1;
+
+		if (ct->fp->f_path.dentry)
+			inode = ctx_inode(ct);
+		if (inode && S_ISBLK(inode->i_mode)) {
+			if (bdev_read_only(inode->i_bdev))
+				ct->ro = 1;
+		} else if (!inode || !S_ISREG(inode->i_mode)) {
+			printk(KERN_DEBUG "invalid file type: %s\n",
+			       ct->this_entity->name);
+			goto file_err;
+		}
+		if (!ct->fp->f_op) {
+			printk(KERN_DEBUG "invalid file: %s\n",
+			       ct->this_entity->name);
+			goto file_err;
+		}
+
+		if (!(ct->fp->f_op->read || ct->fp->f_op->aio_read)) {
+			printk(KERN_DEBUG "file not readable: %s\n",
+			       ct->this_entity->name);
+			ct->wo = 1;
+		}
+		if (!(ct->fp->f_op->write || ct->fp->f_op->aio_write)) {
+			printk(KERN_DEBUG "file not writable: %s\n",
+			       ct->this_entity->name);
+			ct->ro = 1;
+		}
+
+		ct->length = i_size_read(inode->i_mapping->host);
+
+	}
+
+	printk(KERN_DEBUG "Prepared entity %s\n", ct->this_entity->name);
+	return 0;
+
+file_err:
+	ct->fp = NULL;
+	return -1;
+}
+
+static int finish(void *ctx, u8 mode)
+{
+	struct dfu_entity_ctx *ct = ctx;
+
+	if (mode == DFU_WRITE && ct->fp) {
+		int ret;
+
+		if (ct->buffered > 0)
+			ct->write(ct->fp, ct->buf, ct->buffered,
+				  ct->num_done);
+		ret = mutex_lock_interruptible(&ctx_inode(ct)->i_mutex);
+		if (!ret) {
+			struct iattr newattrs;
+
+			ct->length = ct->num_done + ct->buffered;
+			newattrs.ia_size = ct->length;
+			newattrs.ia_valid = ATTR_SIZE | ATTR_MTIME | ATTR_CTIME;
+			if (ct->fp) {
+				newattrs.ia_file = ct->fp;
+				newattrs.ia_valid |= ATTR_FILE;
+			}
+
+			/* Remove suid/sgid on truncate too */
+			ret = should_remove_suid(ct->fp->f_path.dentry);
+			if (ret)
+				newattrs.ia_valid |= ret | ATTR_FORCE;
+
+			ret = notify_change(ct->fp->f_path.dentry, &newattrs);
+
+			mutex_unlock(&ctx_inode(ct)->i_mutex);
+		}
+	}
+
+	ct->prepared = false;
+	printk(KERN_DEBUG "Finished entity %s\n", ct->this_entity->name);
+
+	return 0;
+}
+
+static int read_file(struct file *fp, void *buf, unsigned int len,
+		     unsigned long from)
+{
+	loff_t offs = from;
+
+	return vfs_read(fp, (char __user *)buf, len, &offs);
+}
+
+static int write_file(struct file *fp, void *buf, unsigned int len,
+		      unsigned long from)
+{
+	loff_t offs = from;
+
+	return vfs_write(fp, (char __user *)buf, len, &offs);
+}
+/*
+ * Adapt transport layer buffer size to storage chunk size
+ *
+ * return < n to indicate no more data to read
+ */
+static int read_block(void *ctx, unsigned int n, void *buf)
+{
+	struct dfu_entity_ctx *ct = ctx;
+	unsigned int nread = 0;
+
+	if (n == 0 || !ct->fp || ct->wo)
+		return 0;
+
+	while (nread < n) {
+		unsigned int copy;
+
+		if (ct->num_done >= ct->length)
+			break;
+		if (ct->buffered == 0) {
+			ct->num_read = (unsigned int) min((loff_t)ct->buf_len,
+						ct->length - ct->num_done);
+			ct->read(ct->fp, ct->buf, ct->num_read, ct->num_done);
+			ct->buffered = ct->num_read;
+		}
+		copy = min(n - nread, ct->buffered);
+
+		memcpy(buf + nread, ct->buf + ct->num_read - ct->buffered,
+		       copy);
+		nread += copy;
+		ct->buffered -= copy;
+		ct->num_done += copy;
+	}
+	return nread;
+}
+
+/*
+ * Adapt transport layer buffer size to storage chunk size
+ */
+static int write_block(void *ctx, unsigned int n, void *buf)
+{
+	struct dfu_entity_ctx *ct = ctx;
+	unsigned int nwritten = 0;
+
+	if (n == 0 || !ct->fp || ct->ro)
+		return 0;
+
+	while (nwritten < n) {
+		unsigned int copy;
+
+		if (ct->full)
+			break;
+		if (ct->buffered >= ct->buf_len) {
+			int rc = ct->write(ct->fp, ct->buf, ct->buf_len,
+					   ct->num_done);
+			if (rc == 0) {
+				ct->full = true;
+				break;
+			}
+			ct->buffered = 0;
+			ct->num_done += ct->buf_len;
+		}
+		copy = min(n - nwritten, ct->buf_len - ct->buffered);
+
+		memcpy(ct->buf + ct->buffered, buf + nwritten, copy);
+		nwritten += copy;
+		ct->buffered += copy;
+	}
+
+	return nwritten;
+}
+
+static int build_entities(struct dfu_entity *ents,
+			  struct dfu_entity_ctx *ctxs, unsigned int n)
+{
+	int i;
+	for (i = 0; i < n; ++i) {
+		char *s;
+
+		s = kzalloc(15 * sizeof(char), GFP_KERNEL);
+		if (!s)
+			goto nomem;
+		strcpy(s, "<unconfigured>");
+
+		ctxs[i].buf = file_buf;
+		ctxs[i].buf_len = FILE_BUF_LEN;
+		ctxs[i].read = read_file;
+		ctxs[i].write = write_file;
+		ctxs[i].this_entity = &ents[i];
+
+		ents[i].name = s;
+		ents[i].ctx = &ctxs[i];
+		ents[i].prepare = prepare;
+		ents[i].finish = finish;
+		ents[i].read_block = read_block;
+		ents[i].write_block = write_block;
+	}
+
+	return 0;
+nomem:
+	while (--i)
+		kfree(ents[i].name);
+	return -ENOMEM;
+}
+
+ssize_t attr_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int alt = (int) dev_get_drvdata(dev);
+	sprintf(buf, "%s", the_dfu->entities[alt].name);
+
+	return strlen(buf) + 1;
+}
+
+ssize_t attr_store(struct device *dev, struct device_attribute *attr,
+		   const char *buf, size_t count)
+{
+	int alt = (int) dev_get_drvdata(dev);
+	struct dfu_entity_ctx *ctx = &the_dfu->contexts[alt];
+	char *name;
+	int rc;
+
+	do
+		rc = mutex_lock_interruptible(&ctx->file_mutex);
+	while (rc);
+	name = kzalloc((count + 1) * sizeof(char), GFP_KERNEL);
+	if (name) {
+		strcpy(name, buf);
+		kfree(the_dfu->entities[alt].name);
+		the_dfu->entities[alt].name = name;
+
+		name = kzalloc((count + 1) * sizeof(char), GFP_KERNEL);
+		if (name) {
+			strcpy(name, buf);
+			kfree(the_dfu->strings[STRING_ALTSETTING_BASE + alt].s);
+			the_dfu->strings[STRING_ALTSETTING_BASE + alt].s = name;
+		}
+	}
+	if (ctx->fp) {
+		filp_close(ctx->fp, current->files);
+		ctx->fp = NULL;
+	}
+	reset_context(ctx);
+	mutex_unlock(&ctx->file_mutex);
+
+	return count;
+}
+
+DEVICE_ATTR(fname, 0644, attr_show, attr_store);
+
+static int dfu_storage_thread(void *data)
+{
+	struct dfu_entity *de;
+	struct dfu_entity_ctx *ctx;
+	struct dfu_dev *dev = data;
+	int n, rc;
+
+	allow_signal(SIGINT);
+	allow_signal(SIGTERM);
+	allow_signal(SIGKILL);
+
+	set_freezable();
+	set_current_state(TASK_INTERRUPTIBLE);
+	while (true) {
+		try_to_freeze();
+
+		/* no other threads do the below check */
+		if (atomic_read(&dev->upload_count)) {
+			atomic_dec(&dev->upload_count);
+			de = &dev->entities[dev->altsetting];
+			ctx = de->ctx;
+			do
+				rc = mutex_lock_interruptible(&ctx->file_mutex);
+			while (rc);
+			if (!ctx_prepared(de->ctx)) {
+				printk(KERN_DEBUG "Upload from entity %s\n",
+				       de->name);
+				de->prepare(de->ctx, DFU_READ);
+			}
+			n = de->read_block(de->ctx, dev->upload_len,
+					   dev->req->buf);
+
+			/* no more data to read from this entity */
+			if (n < dev->upload_len)
+				de->finish(de->ctx, DFU_READ);
+			dev->req->length = n;
+			dev->req->zero = dev->req->length < dev->upload_len;
+			usb_ep_queue(dev->gadget->ep0, dev->req, GFP_ATOMIC);
+			wait_for_completion(&dev->upload_completion);
+			if (n >= 0 && n < dev->upload_len)
+				dev->dfu_state = DFU_STATE_dfuIDLE;
+			mutex_unlock(&ctx->file_mutex);
+		}
+		/* no other threads do the below check */
+		if (atomic_read(&dev->cfg_chng_pending)) {
+			atomic_dec(&dev->cfg_chng_pending);
+			spin_lock(&dev->lock);
+			if (dev->do_reset)
+				dfu_reset_config(dev);
+			dfu_set_config(dev, dev->new_config);
+			dev->altsetting = dev->new_altsetting;
+			spin_unlock(&dev->lock);
+			dev->req->length = 0;
+			dev->req->zero = 0 < dev->old_w_length;
+			usb_ep_queue(dev->gadget->ep0, dev->req, GFP_ATOMIC);
+		}
+		/* no other threads do the below check */
+		if (atomic_read(&dev->dnload_count)) {
+			atomic_dec(&dev->dnload_count);
+			dev->thr_rq->length = dev->dnload_len;
+			dev->thr_rq->zero =
+				dev->thr_rq->length < dev->dnload_len;
+			usb_ep_queue(dev->gadget->ep0, dev->thr_rq, GFP_ATOMIC);
+			wait_for_completion(&dev->dnload_completion);
+			de = &dev->entities[dev->altsetting];
+			ctx = de->ctx;
+			do
+				rc = mutex_lock_interruptible(&ctx->file_mutex);
+			while (rc);
+			if (!ctx_prepared(de->ctx)) {
+				printk(KERN_DEBUG "Download to entity %s\n",
+				       de->name);
+				de->prepare(de->ctx, DFU_WRITE);
+			}
+			if (dev->thr_rq->length > 0)
+				de->write_block(de->ctx, dev->thr_rq->length,
+						dev->thr_rq->buf);
+			else
+				de->finish(de->ctx, DFU_WRITE);
+			mutex_unlock(&ctx->file_mutex);
+		}
+
+		if (signal_pending(current)) {
+			set_bit(TERMINATED, &dev->atomic_flags);
+			break;
+		}
+		schedule();
+		set_current_state(TASK_INTERRUPTIBLE);
+	}
+	if (test_and_clear_bit(REGISTERED, &dev->atomic_flags))
+		usb_gadget_unregister_driver(&dfu_driver);
+
+	complete_and_exit(&dev->thread_completion, 0);
+}
+
+/*
+ *
+ * DFU protocol
+ *
+ */
+static void dnload_request_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct dfu_dev *dev = req->context;
+
+	complete(&dev->dnload_completion);
+}
+
+static void upload_request_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct dfu_dev *dev = req->context;
+
+	complete(&dev->upload_completion);
+}
+
+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;
+
+	atomic_inc(&dev->upload_count);
+	req->complete = upload_request_complete;
+	dev->upload_len = len;
+
+	wake_up_process(dev->thread);
+
+	return -1;
+}
+
+static int handle_dnload(struct usb_gadget *gadget, u16 len)
+{
+	struct dfu_dev *dev = get_gadget_data(gadget);
+	struct usb_request *req = dev->thr_rq;
+
+	if (len == 0)
+		dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
+
+	atomic_inc(&dev->dnload_count);
+	req->complete = dnload_request_complete;
+	dev->dnload_len = len;
+
+	wake_up_process(dev->thread);
+
+	return -1;
+}
+
+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);
+
+	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:
+			return handle_upload(req, len);
+		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;
+}
+
+/*
+ *
+ * USB gadget
+ *
+ */
+static inline 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;
+	atomic_set(&dev->dnload_count, 0);
+	atomic_set(&dev->upload_count, 0);
+	del_timer(&dev->resume);
+
+	reset_context(dev->entities[dev->altsetting].ctx);
+}
+
+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 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(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(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);
+			dev->do_reset = false;
+			dev->new_config = w_value;
+			atomic_inc(&dev->cfg_chng_pending);
+			dev->old_w_length = w_length;
+			value = -1;
+			spin_unlock(&dev->lock);
+			wake_up_process(dev->thread);
+			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;
+
+		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;
+				dev->do_reset = true;
+				dev->new_config = config;
+				dev->new_altsetting = w_value;
+				atomic_inc(&dev->cfg_chng_pending);
+				dev->old_w_length = w_length;
+				value = -1;
+			}
+			spin_unlock(&dev->lock);
+			wake_up_process(dev->thread);
+			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);
+		if (value < 0)
+			return 0;
+	}
+
+	/* 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 void dfu_suspend(struct usb_gadget *gadget)
+{
+	struct dfu_dev *dev = get_gadget_data(gadget);
+
+	if (gadget->speed == USB_SPEED_UNKNOWN)
+		return;
+
+	DBG(dev, "suspend\n");
+}
+
+static void dfu_resume(struct usb_gadget *gadget)
+{
+	struct dfu_dev *dev = get_gadget_data(gadget);
+
+	DBG(dev, "resume\n");
+	del_timer(&dev->resume);
+}
+
+static void dfu_autoresume(unsigned long _dev)
+{
+	struct dfu_dev *dev = (struct dfu_dev *) _dev;
+	int status;
+
+	if (dev->gadget->speed != USB_SPEED_UNKNOWN) {
+		status = usb_gadget_wakeup(dev->gadget);
+		DBG(dev, "wakeup --> %d\n", status);
+	}
+}
+
+static void empty_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	/* intentionally empty */
+}
+
+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 dfu_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 empty_release(struct device *dev)
+{
+	/* intentionally empty */
+}
+
+static void dfu_release(struct kref *ref)
+{
+	struct dfu_dev *dfu = container_of(ref, struct dfu_dev, ref);
+	kfree(dfu->entities);
+	kfree(dfu->contexts);
+	kfree(dfu);
+}
+
+static void release_dev(struct device *dev)
+{
+	kref_put(&the_dfu->ref, dfu_release);
+}
+
+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_entities;
+		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_entities;
+		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);
+	}
+	if (dev->thr_rq) {
+		dev->thr_rq->length = USB_BUFSIZ;
+		kfree(dev->thr_rq->buf);
+		usb_ep_free_request(gadget->ep0, dev->thr_rq);
+	}
+	del_timer_sync(&dev->resume);
+	set_gadget_data(gadget, NULL);
+
+	for (i = 0; i < num_entities; ++i) {
+		if (dev->contexts[i].fp) {
+			filp_close(dev->contexts[i].fp, current->files);
+			dev->contexts[i].fp = NULL;
+		}
+		if (dev->contexts[i].sysfs_attr)
+			device_remove_file(&dev->contexts[i].dev,
+					   &dev_attr_fname);
+		if (dev->contexts[i].registered)
+			device_unregister(&dev->contexts[i].dev);
+	}
+	if (dev->sysfs_dfu_reg)
+		device_unregister(&dev->sysfs_dfu);
+
+	if (!test_bit(TERMINATED, &dev->atomic_flags)) {
+		send_sig_info(SIGKILL, SEND_SIG_FORCED, dev->thread);
+		wait_for_completion(&dev->thread_completion);
+
+		complete(&dev->thread_completion);
+	}
+
+}
+
+static int __init dfu_bind(struct usb_gadget *gadget)
+{
+	struct dfu_dev *dev = the_dfu;
+	int i, rc;
+
+	usb_ep_autoconfig_reset(gadget);
+
+	i = usb_gadget_controller_number(gadget);
+	if (i >= 0)
+		device_desc.bcdDevice = cpu_to_le16(0x0200 + i);
+	else {
+		pr_warning("%s: controller '%s' not recognized\n",
+			shortname, gadget->name);
+		device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+	}
+
+	spin_lock_init(&dev->lock);
+	dev->gadget = gadget;
+	set_gadget_data(gadget, dev);
+
+	init_timer(&dev->resume);
+	dev->resume.function = dfu_autoresume;
+	dev->resume.data = (unsigned long) 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;
+
+	dev->thr_rq = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
+	if (!dev->thr_rq)
+		goto enomem;
+	dev->thr_rq->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
+	if (!dev->thr_rq->buf)
+		goto enomem;
+	dev->thr_rq->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;
+
+	if (!dfu_prepare_function(dev, num_entities))
+		goto enomem;
+
+	if (!dfu_prepare_strings(dev, dev->entities, num_entities))
+		goto enomem;
+	stringtab_dfu.strings = dev->strings;
+
+	gadget->ep0->driver_data = dev;
+	dev->req->context = dev;
+	dev->thr_rq->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);
+
+	atomic_set(&dev->dnload_count, 0);
+	atomic_set(&dev->upload_count, 0);
+	init_completion(&dev->dnload_completion);
+	init_completion(&dev->upload_completion);
+
+	dev->sysfs_dfu.parent = &gadget->dev;
+	dev->sysfs_dfu.driver = &dfu_driver.driver;
+	dev->sysfs_dfu.release = empty_release;
+	dev_set_name(&dev->sysfs_dfu, "dfu");
+	rc = device_register(&dev->sysfs_dfu);
+	if (rc)
+		printk(KERN_DEBUG "sysfs device creation failed\n");
+	else {
+		for (i = 0; i < num_entities; ++i) {
+			int rc;
+
+			mutex_init(&dev->contexts[i].file_mutex);
+			dev->contexts[i].dev.parent = &dev->sysfs_dfu;
+			dev->contexts[i].dev.driver = &dfu_driver.driver;
+			dev->contexts[i].dev.release = release_dev;
+			dev_set_name(&dev->contexts[i].dev, "alt%d", i);
+			dev_set_drvdata(&dev->contexts[i].dev, (void *) i);
+			rc = device_register(&dev->contexts[i].dev);
+			if (rc) {
+				printk(KERN_DEBUG "device_register fail for "
+				       "alt%d\n", i);
+			} else {
+				dev->contexts[i].registered = 1;
+				rc = device_create_file(&dev->contexts[i].dev,
+							&dev_attr_fname);
+				if (rc)
+					printk(KERN_DEBUG "device_create_file "
+					       "fail for alt%d\n", i);
+				else {
+					dev->contexts[i].sysfs_attr = 1;
+					kref_get(&dev->ref);
+				}
+			}
+		}
+		dev->sysfs_dfu_reg = 1;
+	}
+
+	set_bit(REGISTERED, &dev->atomic_flags);
+	clear_bit(TERMINATED, &dev->atomic_flags);
+
+	dev->thread = kthread_create(dfu_storage_thread, dev, "dfu-stor");
+	wake_up_process(dev->thread);
+	return 0;
+
+enomem:
+	dfu_unbind(gadget);
+	complete(&dev->thread_completion);
+	return -ENOMEM;
+}
+
+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,
+	.unbind		= __exit_p(dfu_unbind),
+
+	.setup		= dfu_setup,
+	.disconnect	= dfu_disconnect,
+
+	.suspend	= dfu_suspend,
+	.resume		= dfu_resume,
+
+	.driver		= {
+		.name		= (char *) shortname,
+		.owner		= THIS_MODULE,
+	},
+};
+
+MODULE_AUTHOR("Andrzej Pietrasiewicz");
+MODULE_LICENSE("GPL");
+
+static int __init dfu_alloc(unsigned int n)
+{
+	the_dfu = kzalloc(sizeof(*the_dfu), GFP_KERNEL);
+	if (!the_dfu)
+		return -ENOMEM;
+
+	the_dfu->entities = kzalloc(n * sizeof(struct dfu_entity),
+				    GFP_KERNEL);
+	if (!the_dfu->entities)
+		goto dfu_alloc_rollback;
+
+	the_dfu->contexts = kzalloc(n * sizeof(struct dfu_entity_ctx),
+				    GFP_KERNEL);
+	if (!the_dfu->contexts)
+		goto entities_alloc_rollback;
+
+	kref_init(&the_dfu->ref);
+
+	return 0;
+
+entities_alloc_rollback:
+	kfree(the_dfu->entities);
+
+dfu_alloc_rollback:
+	kfree(the_dfu);
+
+	return -ENOMEM;
+}
+
+static int __init init(void)
+{
+	int rc;
+
+	rc = dfu_alloc(num_entities);
+	if (rc)
+		return rc;
+
+	rc = build_entities(the_dfu->entities, the_dfu->contexts, num_entities);
+
+	if (rc) {
+		kref_put(&the_dfu->ref, dfu_release);
+		return rc;
+	}
+
+	init_completion(&the_dfu->thread_completion);
+	rc = usb_gadget_probe_driver(&dfu_driver, dfu_bind);
+
+	if (rc)
+		kref_put(&the_dfu->ref, dfu_release);
+
+	return rc;
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+	if (test_and_clear_bit(REGISTERED, &the_dfu->atomic_flags))
+		usb_gadget_unregister_driver(&dfu_driver);
+
+	wait_for_completion(&the_dfu->thread_completion);
+
+	kref_put(&the_dfu->ref, dfu_release);
+}
+module_exit(cleanup);
+
diff --git a/include/linux/usb/dfu.h b/include/linux/usb/dfu.h
new file mode 100644
index 0000000..99c40d1
--- /dev/null
+++ b/include/linux/usb/dfu.h
@@ -0,0 +1,92 @@
+/*
+ * dfu.h -- Device Firmware Upgrade protocol constants and structures
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
+ *
+ * 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
+ *
+ * http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf
+ */
+
+#ifndef DFU_H_
+#define DFU_H_
+
+/* 3. Requests */
+#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
+
+/* 4.1.3 Run-Time DFU Functional Descriptor */
+#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 << 0)
+
+struct dfu_function_descriptor {
+	__u8				bLength;
+	__u8				bDescriptorType;
+	__u8				bmAttributes;
+	__le16				wDetachTimeOut;
+	__le16				wTransferSize;
+	__le16				bcdDFUVersion;
+} __packed;
+
+/* 6.1.2 DFU_GETSTATUS Request */
+#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,
+};
+
+struct dfu_status {
+	__u8				bStatus;
+	__u8				bwPollTimeout[3];
+	__u8				bState;
+	__u8				iString;
+} __packed;
+
+#endif /* DFU_H_ */
-- 
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux