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