Signed-off-by: Andrzej Pietrasiewicz <andrze...@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.p...@samsung.com> --- drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/dfu.c | 920 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/dfu.h | 171 ++++++++ include/dfu.h | 28 ++ include/flash_entity.h | 39 ++ 5 files changed, 1159 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/dfu.c create mode 100644 drivers/usb/gadget/dfu.h create mode 100644 include/dfu.h create mode 100644 include/flash_entity.h
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index cd22bbe..4b173e2 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libusb_gadget.o # new USB gadget layer dependencies ifdef CONFIG_USB_GADGET COBJS-y += epautoconf.o config.o usbstring.o +COBJS-$(CONFIG_DFU_GADGET) += dfu.o COBJS-$(CONFIG_USB_GADGET_S3C_UDC_OTG) += s3c_udc_otg.o endif ifdef CONFIG_USB_ETHER diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c new file mode 100644 index 0000000..535e194 --- /dev/null +++ b/drivers/usb/gadget/dfu.c @@ -0,0 +1,920 @@ +/* + * dfu.c -- Device Firmware Update gadget + * + * Copyright (C) 2011 Samsung Electronics + * author: Andrzej Pietrasiewicz <andrze...@samsung.com> + * + * Based on gadget zero: + * Copyright (C) 2003-2007 David Brownell + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define VERBOSE_DEBUG +#define DEBUG + +/* +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/device.h> +*/ + +#include <common.h> +#include <asm-generic/errno.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <flash_entity.h> + +#include "gadget_chips.h" +/* #include "epautoconf.c" */ +/* #include "config.c" */ +/* #include "usbstring.c" */ + +#include <malloc.h> +#include "dfu.h" + +static struct flash_entity *flash_ents; +static int num_flash_ents; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0100), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .idVendor = __constant_cpu_to_le16(DRIVER_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(DRIVER_PRODUCT_NUM), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIAL, + .bNumConfigurations = 1, +}; + +static struct usb_config_descriptor dfu_config = { + .bLength = sizeof dfu_config, + .bDescriptorType = USB_DT_CONFIG, + /* compute wTotalLength on the fly */ + .bNumInterfaces = 1, + .bConfigurationValue = DFU_CONFIG_VAL, + .iConfiguration = STRING_DFU_NAME, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 1, /* self-powered */ +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + .bmAttributes = USB_OTG_SRP, +}; + +static const struct dfu_function_descriptor dfu_func = { + .bLength = sizeof dfu_func, + .bDescriptorType = DFU_DT_FUNC, + .bmAttributes = DFU_BIT_WILL_DETACH | + DFU_BIT_MANIFESTATION_TOLERANT | + DFU_BIT_CAN_UPLOAD | + DFU_BIT_CAN_DNLOAD, + .wDetachTimeOut = 0, + .wTransferSize = USB_BUFSIZ, + .bcdDFUVersion = __constant_cpu_to_le16(0x0110), +}; + +static const struct usb_interface_descriptor dfu_intf_runtime = { + .bLength = sizeof dfu_intf_runtime, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_APP_SPEC, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, + .iInterface = STRING_DFU_NAME, +}; + +static const struct usb_descriptor_header *dfu_function_runtime[] = { + (struct usb_descriptor_header *) &otg_descriptor, + (struct usb_descriptor_header *) &dfu_func, + (struct usb_descriptor_header *) &dfu_intf_runtime, + NULL, +}; + +static struct usb_qualifier_descriptor dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .bNumConfigurations = 1, +}; + +static char manufacturer[50]; +static const char longname[] = "DFU Gadget"; +/* default serial number takes at least two packets */ +static const char serial[] = "0123456789.0123456789.012345678"; +static const char dfu_name[] = "Device Firmware Upgrade"; +static const char shortname[] = "dfu"; + +/* static strings, in UTF-8 */ +static struct usb_string strings_runtime[] = { + { STRING_MANUFACTURER, manufacturer, }, + { STRING_PRODUCT, longname, }, + { STRING_SERIAL, serial, }, + { STRING_DFU_NAME, dfu_name, }, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_runtime = { + .language = 0x0409, /* en-us */ + .strings = strings_runtime, +}; + +static struct usb_gadget_strings stringtab_dfu = { + .language = 0x0409, /* en-us */ +}; + +static bool is_runtime(struct dfu_dev *dev) +{ + return dev->dfu_state == DFU_STATE_appIDLE || + dev->dfu_state == DFU_STATE_appDETACH; +} + +static int config_buf(struct usb_gadget *gadget, + u8 *buf, u8 type, unsigned index) +{ + int hs = 0; + struct dfu_dev *dev = get_gadget_data(gadget); + const struct usb_descriptor_header **function; + int len; + + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(gadget)) { + hs = (gadget->speed == USB_SPEED_HIGH); + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + } + if (is_runtime(dev)) + function = dfu_function_runtime; + else + function = (const struct usb_descriptor_header **)dev->function; + + /* for now, don't advertise srp-only devices */ + if (!gadget_is_otg(gadget)) + function++; + + len = usb_gadget_config_buf(&dfu_config, + buf, USB_BUFSIZ, function); + if (len < 0) + return len; + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + +static void dfu_reset_config(struct dfu_dev *dev) +{ + if (dev->config == 0) + return; + + DBG(dev, "reset config\n"); + + dev->config = 0; +} + +static int dfu_set_config(struct dfu_dev *dev, unsigned number) +{ + int result = 0; + struct usb_gadget *gadget = dev->gadget; + char *speed; + + if (number == dev->config) + return 0; + + dfu_reset_config(dev); + + if (DFU_CONFIG_VAL != number) { + result = -EINVAL; + return result; + } + + switch (gadget->speed) { + case USB_SPEED_LOW: + speed = "low"; + break; + case USB_SPEED_FULL: + speed = "full"; + break; + case USB_SPEED_HIGH: + speed = "high"; + break; + default: + speed = "?"; + break; + } + + dev->config = number; + INFO(dev, "%s speed config #%d: %s\n", speed, number, dfu_name); + return result; +} + +/*-------------------------------------------------------------------------*/ + +static void empty_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* intentionally empty */ +} + +static void dnload_request_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct dfu_dev *dev = req->context; + struct flash_entity *fe = &flash_ents[dev->altsetting]; + + if (dev->not_prepared) { + printf("DOWNLOAD %s\n", fe->name); + fe->prepare(fe->ctx, FLASH_WRITE); + dev->not_prepared = false; + } + + if (req->length > 0) + fe->write_block(fe->ctx, req->length, req->buf); + else { + fe->finish(fe->ctx, FLASH_WRITE); + dev->not_prepared = true; + } +} + +static void handle_getstatus(struct usb_request *req) +{ + struct dfu_status *dstat = (struct dfu_status *)req->buf; + struct dfu_dev *dev = req->context; + + switch (dev->dfu_state) { + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: + dev->dfu_state = DFU_STATE_dfuDNLOAD_IDLE; + break; + case DFU_STATE_dfuMANIFEST_SYNC: + break; + default: + break; + } + + /* send status response */ + dstat->bStatus = dev->dfu_status; + /* FIXME: set dstat->bwPollTimeout */ + dstat->bState = dev->dfu_state; + dstat->iString = 0; +} + +static void handle_getstate(struct usb_request *req) +{ + struct dfu_dev *dev = req->context; + + ((u8 *)req->buf)[0] = dev->dfu_state & 0xff; + req->actual = sizeof(u8); +} + +static int handle_upload(struct usb_request *req, u16 len) +{ + struct dfu_dev *dev = req->context; + struct flash_entity *fe = &flash_ents[dev->altsetting]; + int n; + + if (dev->not_prepared) { + printf("UPLOAD %s\n", fe->name); + fe->prepare(fe->ctx, FLASH_READ); + dev->not_prepared = false; + } + n = fe->read_block(fe->ctx, len, req->buf); + + /* no more data to read from this entity */ + if (n < len) { + fe->finish(fe->ctx, FLASH_READ); + dev->not_prepared = true; + } + + return n; +} + +static int handle_dnload(struct usb_gadget *gadget, u16 len) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + struct usb_request *req = dev->req; + + if (len == 0) + dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC; + + req->complete = dnload_request_complete; + + return len; +} + +static int +dfu_handle(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + struct usb_request *req = dev->req; + u16 len = le16_to_cpu(ctrl->wLength); + int rc = 0; + + switch (dev->dfu_state) { + case DFU_STATE_appIDLE: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + case USB_REQ_DFU_DETACH: + dev->dfu_state = DFU_STATE_appDETACH; + dev->dfu_state = DFU_STATE_dfuIDLE; + return RET_ZLP; + default: + return RET_STALL; + } + break; + case DFU_STATE_appDETACH: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + default: + dev->dfu_state = DFU_STATE_appIDLE; + return RET_STALL; + } + /* FIXME: implement timer to return to appIDLE */ + break; + case DFU_STATE_dfuIDLE: + switch (ctrl->bRequest) { + case USB_REQ_DFU_DNLOAD: + if (len == 0) { + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + return handle_dnload(gadget, len); + case USB_REQ_DFU_UPLOAD: + dev->dfu_state = DFU_STATE_dfuUPLOAD_IDLE; + return handle_upload(req, len); + case USB_REQ_DFU_ABORT: + /* no zlp? */ + return RET_ZLP; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + case USB_REQ_DFU_DETACH: + /* Proprietary extension: 'detach' from idle mode and + * get back to runtime mode in case of USB Reset. As + * much as I dislike this, we just can't use every USB + * bus reset to switch back to runtime mode, since at + * least the Linux USB stack likes to send a number of + * resets in a row :( + */ + dev->dfu_state = DFU_STATE_dfuMANIFEST_WAIT_RST; + dev->dfu_state = DFU_STATE_appIDLE; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuDNLOAD_SYNC: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + /* FIXME: state transition depending + * on block completeness */ + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuDNBUSY: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + /* FIXME: only accept getstatus if bwPollTimeout + * has elapsed */ + handle_getstatus(req); + return RET_STAT_LEN; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuDNLOAD_IDLE: + switch (ctrl->bRequest) { + case USB_REQ_DFU_DNLOAD: + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + return handle_dnload(gadget, len); + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + return RET_ZLP; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuMANIFEST_SYNC: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + /* We're MainfestationTolerant */ + dev->dfu_state = DFU_STATE_dfuIDLE; + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuMANIFEST: + /* we should never go here */ + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + case DFU_STATE_dfuMANIFEST_WAIT_RST: + /* we should never go here */ + break; + case DFU_STATE_dfuUPLOAD_IDLE: + switch (ctrl->bRequest) { + case USB_REQ_DFU_UPLOAD: + /* state transition if less data then requested */ + rc = handle_upload(req, len); + if (rc >= 0 && rc < len) + dev->dfu_state = DFU_STATE_dfuIDLE; + return rc; + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + /* no zlp? */ + return RET_ZLP; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + case DFU_STATE_dfuERROR: + switch (ctrl->bRequest) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(req); + return RET_STAT_LEN; + case USB_REQ_DFU_GETSTATE: + handle_getstate(req); + break; + case USB_REQ_DFU_CLRSTATUS: + dev->dfu_state = DFU_STATE_dfuIDLE; + dev->dfu_status = DFU_STATUS_OK; + /* no zlp? */ + return RET_ZLP; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + return RET_STALL; + } + break; + default: + return -1; + } + + return 0; +} + +static int +dfu_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + struct usb_request *req = dev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + req->zero = 0; + req->complete = empty_complete; + if (!(ctrl->bRequestType & USB_TYPE_CLASS)) + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + value = min(w_length, (u16) sizeof device_desc); + memcpy(req->buf, &device_desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget)) + break; + value = min(w_length, + (u16) sizeof dev_qualifier); + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget_is_dualspeed(gadget)) + break; + /* FALLTHROUGH */ + case USB_DT_CONFIG: + value = config_buf(gadget, req->buf, + w_value >> 8, + w_value & 0xff); + if (value >= 0) + value = min_t(w_length, (u16) value); + break; + + case USB_DT_STRING: + /* wIndex == language code. */ + value = usb_gadget_get_string( + is_runtime(dev) ? &stringtab_runtime : + &stringtab_dfu, + w_value & 0xff, req->buf); + if (value >= 0) + value = min_t(w_length, (u16) value); + break; + case DFU_DT_FUNC: + value = min(w_length, (u16) sizeof dfu_func); + memcpy(req->buf, &dfu_func, value); + break; + } + break; + + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unknown; + if (gadget->a_hnp_support) + DBG(dev, "HNP available\n"); + else if (gadget->a_alt_hnp_support) + DBG(dev, "HNP needs a different root port\n"); + else + VDBG(dev, "HNP inactive\n"); + spin_lock(&dev->lock); + value = dfu_set_config(dev, w_value); + spin_unlock(&dev->lock); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + *(u8 *)req->buf = dev->config; + value = min(w_length, (u16) 1); + break; + + /* until we add altsetting support, or other interfaces, + * only 0/0 are possible. pxa2xx only supports 0/0 (poorly) + * and already killed pending endpoint I/O. + */ + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != USB_RECIP_INTERFACE) + goto unknown; + spin_lock(&dev->lock); + if (dev->config && w_index == 0) { + u8 config = dev->config; + + /* resets interface configuration, forgets about + * previous transaction state (queued bufs, etc) + * and re-inits endpoint state (toggle etc) + * no response queued, just zero + * status == success. + * if we had more than one interface we couldn't + * use this "reset the config" shortcut. + */ + dfu_reset_config(dev); + dfu_set_config(dev, config); + dev->altsetting = w_value; + value = 0; + } + spin_unlock(&dev->lock); + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != + (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto unknown; + if (!dev->config) + break; + if (w_index != 0) { + value = -EDOM; + break; + } + *(u8 *)req->buf = 0; + value = min(w_length, (u16) 1); + break; + + default: +unknown: + VDBG(dev, + "unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + else + value = dfu_handle(gadget, ctrl); + + /* respond with data transfer before status phase? */ + if (value >= 0) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG(dev, "ep_queue --> %d\n", value); + req->status = 0; + } + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +/*-------------------------------------------------------------------------*/ + +static void dfu_disconnect(struct usb_gadget *gadget) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dfu_reset_config(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int dfu_prepare_function(struct dfu_dev *dev, int n) +{ + struct usb_interface_descriptor *d; + int i = 0; + + dev->function = kzalloc((ALTSETTING_BASE + n + 1) * + sizeof(struct usb_descriptor_header *), + GFP_KERNEL); + if (!dev->function) + goto enomem; + + dev->function[0] = (struct usb_descriptor_header *)&otg_descriptor; + dev->function[1] = (struct usb_descriptor_header *)&dfu_func; + + for (i = 0; i < n; ++i) { + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + goto enomem; + + d->bLength = sizeof(*d); + d->bDescriptorType = USB_DT_INTERFACE; + d->bAlternateSetting = i; + d->bNumEndpoints = 0; + d->bInterfaceClass = USB_CLASS_APP_SPEC; + d->bInterfaceSubClass = 1; + d->bInterfaceProtocol = 2; + d->iInterface = DFU_STR_BASE + i; + + dev->function[ALTSETTING_BASE + i] = + (struct usb_descriptor_header *)d; + } + dev->function[ALTSETTING_BASE + i] = NULL; + + return 1; + +enomem: + while (i) { + kfree(dev->function[--i + ALTSETTING_BASE]); + dev->function[i + ALTSETTING_BASE] = NULL; + } + kfree(dev->function); + + return 0; +} + +static int +dfu_prepare_strings(struct dfu_dev *dev, struct flash_entity *f, int n) +{ + int i = 0; + + dev->strings = kzalloc((STRING_ALTSETTING_BASE + n + 1) * + sizeof(struct usb_string), + GFP_KERNEL); + if (!dev->strings) + goto enomem; + + dev->strings[0].id = STRING_MANUFACTURER; + dev->strings[0].s = manufacturer; + dev->strings[1].id = STRING_PRODUCT; + dev->strings[1].s = longname; + dev->strings[2].id = STRING_SERIAL; + dev->strings[2].s = serial; + dev->strings[3].id = STRING_DFU_NAME; + dev->strings[3].s = dfu_name; + + for (i = 0; i < n; ++i) { + char *s; + + dev->strings[STRING_ALTSETTING_BASE + i].id = DFU_STR_BASE + i; + s = kzalloc(strlen(f[i].name) + 1, GFP_KERNEL); + if (!s) + goto enomem; + + strcpy(s, f[i].name); + dev->strings[STRING_ALTSETTING_BASE + i].s = s; + } + dev->strings[STRING_ALTSETTING_BASE + i].id = 0; + dev->strings[STRING_ALTSETTING_BASE + i].s = NULL; + + return 1; + +enomem: + while (i) { + kfree((void *)dev->strings[--i + STRING_ALTSETTING_BASE].s); + dev->strings[i + STRING_ALTSETTING_BASE].s = NULL; + } + kfree(dev->strings); + + return 0; +} + +static void dfu_unbind(struct usb_gadget *gadget) +{ + struct dfu_dev *dev = get_gadget_data(gadget); + int i; + + DBG(dev, "unbind\n"); + + if (dev->strings) { + i = num_flash_ents; + while (i) { + kfree((void *) + dev->strings[--i + STRING_ALTSETTING_BASE].s); + dev->strings[i + STRING_ALTSETTING_BASE].s = NULL; + } + kfree(dev->strings); + } + if (dev->function) { + i = num_flash_ents; + while (i) { + kfree(dev->function[--i + ALTSETTING_BASE]); + dev->function[i + ALTSETTING_BASE] = NULL; + } + kfree(dev->function); + } + /* we've already been disconnected ... no i/o is active */ + if (dev->req) { + dev->req->length = USB_BUFSIZ; + kfree(dev->req->buf); + usb_ep_free_request(gadget->ep0, dev->req); + } + kfree(dev); + set_gadget_data(gadget, NULL); +} + +static int __init dfu_bind(struct usb_gadget *gadget) +{ + struct dfu_dev *dev; + int gcnum; + + usb_ep_autoconfig_reset(gadget); + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum); + else { + pr_warning("%s: controller '%s' not recognized\n", + shortname, gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + spin_lock_init(&dev->lock); + dev->gadget = gadget; + set_gadget_data(gadget, dev); + + dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto enomem; + dev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL); + if (!dev->req->buf) + goto enomem; + + dev->req->complete = empty_complete; + + device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + + if (gadget_is_dualspeed(gadget)) + dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0; + + if (gadget_is_otg(gadget)) { + otg_descriptor.bmAttributes |= USB_OTG_HNP, + dfu_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + usb_gadget_set_selfpowered(gadget); + + dev->dfu_state = DFU_STATE_appIDLE; + dev->dfu_status = DFU_STATUS_OK; + dev->not_prepared = true; + + if (!dfu_prepare_function(dev, num_flash_ents)) + goto enomem; + + if (!dfu_prepare_strings(dev, flash_ents, num_flash_ents)) + goto enomem; + stringtab_dfu.strings = dev->strings; + + gadget->ep0->driver_data = dev; + dev->req->context = dev; + + INFO(dev, "%s, version: " DRIVER_VERSION "\n", longname); + + /* snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); */ + + return 0; + +enomem: + dfu_unbind(gadget); + return -ENOMEM; +} + +static void dfu_suspend(struct usb_gadget *gadget) +{ + if (gadget->speed == USB_SPEED_UNKNOWN) + return; + + DBG(dev, "suspend\n"); +} + +static void dfu_resume(struct usb_gadget *gadget) +{ + DBG(dev, "resume\n"); +} + +static struct usb_gadget_driver dfu_driver = { +#ifdef CONFIG_USB_GADGET_DUALSPEED + .speed = USB_SPEED_HIGH, +#else + .speed = USB_SPEED_FULL, +#endif + /*.function = (char *) longname,*/ + .bind = dfu_bind, + .unbind = __exit_p(dfu_unbind), + + .setup = dfu_setup, + .disconnect = dfu_disconnect, + + .suspend = dfu_suspend, + .resume = dfu_resume, + + /* + .driver = { + .name = (char *) shortname, + .owner = THIS_MODULE, + },*/ +}; + +void register_flash_entities(struct flash_entity *flents, int n) +{ + flash_ents = flents; + num_flash_ents = n; +} + +int __init dfu_init(void) +{ + return usb_gadget_register_driver(&dfu_driver); +} +module_init(dfu_init); + +void __exit dfu_cleanup(void) +{ + usb_gadget_unregister_driver(&dfu_driver); +} +module_exit(cleanup); + diff --git a/drivers/usb/gadget/dfu.h b/drivers/usb/gadget/dfu.h new file mode 100644 index 0000000..5559273 --- /dev/null +++ b/drivers/usb/gadget/dfu.h @@ -0,0 +1,171 @@ +/* + * dfu.h -- Device Firmware Update gadget + * + * Copyright (C) 2011 Samsung Electronics + * author: Andrzej Pietrasiewicz <andrze...@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DFU_H_ +#define DFU_H_ + +#include <linux/compiler.h> + +/* + * Linux kernel compatibility layer + */ +#define GFP_ATOMIC ((gfp_t) 0) +#define GFP_KERNEL ((gfp_t) 0) +#define true 1 +#define false 0 +#define dev_dbg(...) do {} while (0) +#define dev_vdbg(...) do {} while (0) +#define dev_err(...) do {} while (0) +#define dev_warn(...) do {} while (0) +#define dev_info(...) do {} while (0) +#define pr_warning(...) do {} while (0) +#define spin_lock_init(lock) do {} while (0) +#define spin_lock(lock) do {} while (0) +#define spin_unlock(lock) do {} while (0) +#define spin_lock_irqsave(lock,flags) do {flags = 1;} while (0) +#define spin_unlock_irqrestore(lock,flags) do {flags = 0;} while (0) +#define kmalloc(x,y) malloc(x) +#define kfree(x) free(x) +#define kzalloc(size,flags) calloc((size), 1) +#define __init +#define __exit +#define __exit_p(x) x +#define module_init(...) +#define module_exit(...) +#define min_t min +#define spinlock_t int +#define bool int +/* + * end compatibility layer + */ + +#define DBG(d, fmt, args...) \ + dev_dbg(&(d)->gadget->dev , fmt , ## args) +#define VDBG(d, fmt, args...) \ + dev_vdbg(&(d)->gadget->dev , fmt , ## args) +#define ERROR(d, fmt, args...) \ + dev_err(&(d)->gadget->dev , fmt , ## args) +#define WARN(d, fmt, args...) \ + dev_warn(&(d)->gadget->dev , fmt , ## args) +#define INFO(d, fmt, args...) \ + dev_info(&(d)->gadget->dev , fmt , ## args) + +#define DRIVER_VERSION "Msciwoj" + +/* Thanks to NetChip Technologies for donating this product ID. */ +#define DRIVER_VENDOR_NUM 0x0525 /* NetChip */ +#define DRIVER_PRODUCT_NUM 0xffff /* DFU */ + +#define STRING_MANUFACTURER 0 +#define STRING_PRODUCT 1 +#define STRING_SERIAL 2 +#define STRING_DFU_NAME 49 +#define DFU_STR_BASE 50 + +#define DFU_CONFIG_VAL 1 +#define DFU_DT_FUNC 0x21 + +#define DFU_BIT_WILL_DETACH (0x1 << 3) +#define DFU_BIT_MANIFESTATION_TOLERANT (0x1 << 2) +#define DFU_BIT_CAN_UPLOAD (0x1 << 1) +#define DFU_BIT_CAN_DNLOAD 0x1 + +/* big enough to hold our biggest descriptor */ +#define USB_BUFSIZ 4096 + +#define USB_REQ_DFU_DETACH 0x00 +#define USB_REQ_DFU_DNLOAD 0x01 +#define USB_REQ_DFU_UPLOAD 0x02 +#define USB_REQ_DFU_GETSTATUS 0x03 +#define USB_REQ_DFU_CLRSTATUS 0x04 +#define USB_REQ_DFU_GETSTATE 0x05 +#define USB_REQ_DFU_ABORT 0x06 + +#define DFU_STATUS_OK 0x00 +#define DFU_STATUS_errTARGET 0x01 +#define DFU_STATUS_errFILE 0x02 +#define DFU_STATUS_errWRITE 0x03 +#define DFU_STATUS_errERASE 0x04 +#define DFU_STATUS_errCHECK_ERASED 0x05 +#define DFU_STATUS_errPROG 0x06 +#define DFU_STATUS_errVERIFY 0x07 +#define DFU_STATUS_errADDRESS 0x08 +#define DFU_STATUS_errNOTDONE 0x09 +#define DFU_STATUS_errFIRMWARE 0x0a +#define DFU_STATUS_errVENDOR 0x0b +#define DFU_STATUS_errUSBR 0x0c +#define DFU_STATUS_errPOR 0x0d +#define DFU_STATUS_errUNKNOWN 0x0e +#define DFU_STATUS_errSTALLEDPKT 0x0f + +#define RET_STALL -1 +#define RET_ZLP 0 +#define RET_STAT_LEN 6 + +#define ALTSETTING_BASE 2 +#define STRING_ALTSETTING_BASE 4 + +enum dfu_state { + DFU_STATE_appIDLE = 0, + DFU_STATE_appDETACH = 1, + DFU_STATE_dfuIDLE = 2, + DFU_STATE_dfuDNLOAD_SYNC = 3, + DFU_STATE_dfuDNBUSY = 4, + DFU_STATE_dfuDNLOAD_IDLE = 5, + DFU_STATE_dfuMANIFEST_SYNC = 6, + DFU_STATE_dfuMANIFEST = 7, + DFU_STATE_dfuMANIFEST_WAIT_RST = 8, + DFU_STATE_dfuUPLOAD_IDLE = 9, + DFU_STATE_dfuERROR = 10, +}; + +struct dfu_status { + __u8 bStatus; + __u8 bwPollTimeout[3]; + __u8 bState; + __u8 iString; +} __packed; + +struct dfu_function_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bmAttributes; + __le16 wDetachTimeOut; + __le16 wTransferSize; + __le16 bcdDFUVersion; +} __packed; + +struct dfu_dev { + spinlock_t lock; + struct usb_gadget *gadget; + struct usb_request *req; /* for control responses */ + + /* when configured, we have one config */ + u8 config; + u8 altsetting; + enum dfu_state dfu_state; + unsigned int dfu_status; + struct usb_descriptor_header **function; + struct usb_string *strings; + bool not_prepared; +}; + +#endif /* DFU_H_ */ diff --git a/include/dfu.h b/include/dfu.h new file mode 100644 index 0000000..21b89f2 --- /dev/null +++ b/include/dfu.h @@ -0,0 +1,28 @@ +/* + * dfu.h - Device Firmware Upgrade + * + * copyright (c) 2011 samsung electronics + * author: andrzej pietrasiewicz <andrze...@samsung.com> + * + * this program is free software; you can redistribute it and/or modify + * it under the terms of the gnu general public license as published by + * the free software foundation; either version 2 of the license, or + * (at your option) any later version. + * + * this program is distributed in the hope that it will be useful, + * but without any warranty; without even the implied warranty of + * merchantability or fitness for a particular purpose. see the + * gnu general public license for more details. + * + * you should have received a copy of the gnu general public license + * along with this program; if not, write to the free software + * foundation, inc., 59 temple place, suite 330, boston, ma 02111-1307 usa + */ + +#ifndef __DFU_H__ +#define __DFU_H__ + +extern int dfu_init(void); +extern int dfu_cleanup(void); + +#endif diff --git a/include/flash_entity.h b/include/flash_entity.h new file mode 100644 index 0000000..daa90ee --- /dev/null +++ b/include/flash_entity.h @@ -0,0 +1,39 @@ +/* + * flash_entity.h - flashable area description + * + * Copyright (C) 2011 Samsung Electronics + * author: Andrzej Pietrasiewicz <andrze...@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef FLASH_ENTITY_H_ +#define FLASH_ENTITY_H_ + +#define FLASH_READ 0 +#define FLASH_WRITE 1 + +struct flash_entity { + char *name; + void *ctx; + int (*prepare)(void *ctx, u8 mode); + int (*read_block)(void *ctx, unsigned int n, void *buf); + int (*write_block)(void *ctx, unsigned int n, void *buf); + int (*finish)(void *ctx, u8 mode); +}; + +void register_flash_entities(struct flash_entity *flents, int n); + +#endif /* FLASH_ENTITY_H_ */ -- 1.7.0.4 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot