summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/function/dfu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/function/dfu.c')
-rw-r--r--drivers/usb/gadget/function/dfu.c861
1 files changed, 861 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/dfu.c b/drivers/usb/gadget/function/dfu.c
new file mode 100644
index 0000000000..0b7ca82c4a
--- /dev/null
+++ b/drivers/usb/gadget/function/dfu.c
@@ -0,0 +1,861 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * (C) 2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *
+ * based on existing SAM7DFU code from OpenPCD:
+ * (C) Copyright 2006 by Harald Welte <hwelte@hmw-consulting.de>
+ *
+ * TODO:
+ * - make NAND support reasonably self-contained and put in apropriate
+ * ifdefs
+ * - add some means of synchronization, i.e. block commandline access
+ * while DFU transfer is in progress, and return to commandline once
+ * we're finished
+ * - add VERIFY support after writing to flash
+ * - sanely free() resources allocated during first uppload/download
+ * request when aborting
+ * - sanely free resources when another alternate interface is selected
+ *
+ * Maybe:
+ * - add something like uImage or some other header that provides CRC
+ * checking?
+ * - make 'dnstate' attached to 'struct usb_device_instance'
+ */
+#define pr_fmt(fmt) "dfu: " fmt
+
+#include <dma.h>
+#include <asm/byteorder.h>
+#include <usb/composite.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <usb/gadget.h>
+#include <linux/stat.h>
+#include <libfile.h>
+#include <linux/err.h>
+#include <usb/ch9.h>
+#include <usb/dfu.h>
+#include <config.h>
+#include <common.h>
+#include <malloc.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libbb.h>
+#include <init.h>
+#include <fs.h>
+#include <ioctl.h>
+#include <linux/mtd/mtd-abi.h>
+#include <work.h>
+
+#define USB_DT_DFU 0x21
+
+struct usb_dfu_func_descriptor {
+ u_int8_t bLength;
+ u_int8_t bDescriptorType;
+ u_int8_t bmAttributes;
+#define USB_DFU_CAN_DOWNLOAD (1 << 0)
+#define USB_DFU_CAN_UPLOAD (1 << 1)
+#define USB_DFU_MANIFEST_TOL (1 << 2)
+#define USB_DFU_WILL_DETACH (1 << 3)
+ u_int16_t wDetachTimeOut;
+ u_int16_t wTransferSize;
+ u_int16_t bcdDFUVersion;
+} __attribute__ ((packed));
+
+#define USB_DT_DFU_SIZE 9
+
+#define USB_TYPE_DFU (USB_TYPE_CLASS|USB_RECIP_INTERFACE)
+
+/* DFU class-specific requests (Section 3, DFU Rev 1.1) */
+#define USB_REQ_DFU_DETACH 0x00
+#define USB_REQ_DFU_DNLOAD 0x01
+#define USB_REQ_DFU_UPLOAD 0x02
+#define USB_REQ_DFU_GETSTATUS 0x03
+#define USB_REQ_DFU_CLRSTATUS 0x04
+#define USB_REQ_DFU_GETSTATE 0x05
+#define USB_REQ_DFU_ABORT 0x06
+
+struct dfu_status {
+ u_int8_t bStatus;
+ u_int8_t bwPollTimeout[3];
+ u_int8_t bState;
+ u_int8_t iString;
+} __attribute__((packed));
+
+#define DFU_STATUS_OK 0x00
+#define DFU_STATUS_errTARGET 0x01
+#define DFU_STATUS_errFILE 0x02
+#define DFU_STATUS_errWRITE 0x03
+#define DFU_STATUS_errERASE 0x04
+#define DFU_STATUS_errCHECK_ERASED 0x05
+#define DFU_STATUS_errPROG 0x06
+#define DFU_STATUS_errVERIFY 0x07
+#define DFU_STATUS_errADDRESS 0x08
+#define DFU_STATUS_errNOTDONE 0x09
+#define DFU_STATUS_errFIRMWARE 0x0a
+#define DFU_STATUS_errVENDOR 0x0b
+#define DFU_STATUS_errUSBR 0x0c
+#define DFU_STATUS_errPOR 0x0d
+#define DFU_STATUS_errUNKNOWN 0x0e
+#define DFU_STATUS_errSTALLEDPKT 0x0f
+
+enum dfu_state {
+ DFU_STATE_appIDLE = 0,
+ DFU_STATE_appDETACH = 1,
+ DFU_STATE_dfuIDLE = 2,
+ DFU_STATE_dfuDNLOAD_SYNC = 3,
+ DFU_STATE_dfuDNBUSY = 4,
+ DFU_STATE_dfuDNLOAD_IDLE = 5,
+ DFU_STATE_dfuMANIFEST_SYNC = 6,
+ DFU_STATE_dfuMANIFEST = 7,
+ DFU_STATE_dfuMANIFEST_WAIT_RST = 8,
+ DFU_STATE_dfuUPLOAD_IDLE = 9,
+ DFU_STATE_dfuERROR = 10,
+};
+
+#define USB_DT_DFU_SIZE 9
+#define USB_DT_DFU 0x21
+
+#define CONFIG_USBD_DFU_XFER_SIZE 4096
+#define DFU_TEMPFILE "/dfu_temp"
+
+struct file_list_entry *dfu_file_entry;
+static int dfufd = -EINVAL;
+static struct file_list *dfu_files;
+static int dfudetach;
+static struct mtd_info_user dfu_mtdinfo;
+static loff_t dfu_written;
+static loff_t dfu_erased;
+static int prog_erase;
+
+/* USB DFU functional descriptor */
+static struct usb_dfu_func_descriptor usb_dfu_func = {
+ .bLength = USB_DT_DFU_SIZE,
+ .bDescriptorType = USB_DT_DFU,
+ .bmAttributes = USB_DFU_CAN_UPLOAD | USB_DFU_CAN_DOWNLOAD | USB_DFU_MANIFEST_TOL,
+ .wDetachTimeOut = 0xff00,
+ .wTransferSize = CONFIG_USBD_DFU_XFER_SIZE,
+ .bcdDFUVersion = 0x0100,
+};
+
+struct f_dfu {
+ struct usb_function func;
+ u8 port_num;
+
+ u8 dfu_state;
+ u8 dfu_status;
+ struct usb_request *dnreq;
+ struct work_queue wq;
+};
+
+static inline struct f_dfu *func_to_dfu(struct usb_function *f)
+{
+ return container_of(f, struct f_dfu, func);
+}
+
+/* static strings, in UTF-8 */
+static struct usb_string *dfu_string_defs;
+
+static struct usb_gadget_strings dfu_string_table = {
+ .language = 0x0409, /* en-us */
+};
+
+static struct usb_gadget_strings *dfu_strings[] = {
+ &dfu_string_table,
+ NULL,
+};
+
+static void dn_complete(struct usb_ep *ep, struct usb_request *req);
+static void up_complete(struct usb_ep *ep, struct usb_request *req);
+static void dfu_cleanup(struct f_dfu *dfu);
+
+struct dfu_work {
+ struct work_struct work;
+ struct f_dfu *dfu;
+ void (*task)(struct dfu_work *dw);
+ size_t len;
+ uint8_t *rbuf;
+ uint8_t wbuf[CONFIG_USBD_DFU_XFER_SIZE];
+};
+
+static void dfu_do_work(struct work_struct *w)
+{
+ struct dfu_work *dw = container_of(w, struct dfu_work, work);
+ struct f_dfu *dfu = dw->dfu;
+
+ if (dfu->dfu_state != DFU_STATE_dfuERROR && dfu->dfu_status == DFU_STATUS_OK)
+ dw->task(dw);
+ else
+ pr_debug("skip work\n");
+
+ free(dw);
+}
+
+static void dfu_work_cancel(struct work_struct *w)
+{
+ struct dfu_work *dw = container_of(w, struct dfu_work, work);
+
+ free(dw);
+}
+
+static void dfu_do_write(struct dfu_work *dw)
+{
+ struct f_dfu *dfu = dw->dfu;
+ ssize_t size, wlen = dw->len;
+ ssize_t ret;
+
+ pr_debug("do write\n");
+
+ if (prog_erase && (dfu_written + wlen) > dfu_erased) {
+ size = roundup(wlen, dfu_mtdinfo.erasesize);
+ ret = erase(dfufd, size, dfu_erased);
+ dfu_erased += size;
+ if (ret && ret != -ENOSYS) {
+ perror("erase");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errERASE;
+ return;
+ }
+ }
+
+ dfu_written += wlen;
+ ret = write(dfufd, dw->wbuf, wlen);
+ if (ret < wlen) {
+ perror("write");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errWRITE;
+ }
+}
+
+static void dfu_do_read(struct dfu_work *dw)
+{
+ struct f_dfu *dfu = dw->dfu;
+ struct usb_composite_dev *cdev = dfu->func.config->cdev;
+ ssize_t size, rlen = dw->len;
+
+ pr_debug("do read\n");
+
+ size = read(dfufd, dfu->dnreq->buf, rlen);
+ dfu->dnreq->length = size;
+ if (size < 0) {
+ perror("read");
+ dfu->dnreq->length = 0;
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errFILE;
+ } else if (size < rlen) {
+ /* this is the last chunk, go to IDLE and close file */
+ dfu_cleanup(dfu);
+ }
+
+ dfu->dnreq->complete = up_complete;
+ usb_ep_queue(cdev->gadget->ep0, dfu->dnreq);
+}
+
+static void dfu_do_open_dnload(struct dfu_work *dw)
+{
+ struct f_dfu *dfu = dw->dfu;
+ int ret;
+
+ pr_debug("do open dnload\n");
+
+ if (dfu_file_entry->flags & FILE_LIST_FLAG_SAFE) {
+ dfufd = open(DFU_TEMPFILE, O_WRONLY | O_CREAT);
+ } else {
+ unsigned flags = O_WRONLY;
+
+ if (dfu_file_entry->flags & FILE_LIST_FLAG_CREATE)
+ flags |= O_CREAT | O_TRUNC;
+
+ dfufd = open(dfu_file_entry->filename, flags);
+ }
+
+ if (dfufd < 0) {
+ perror("open");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errFILE;
+ return;
+ }
+
+ if (!(dfu_file_entry->flags & FILE_LIST_FLAG_SAFE)) {
+ ret = ioctl(dfufd, MEMGETINFO, &dfu_mtdinfo);
+ if (!ret) /* file is on a mtd device */
+ prog_erase = 1;
+ }
+}
+
+static void dfu_do_open_upload(struct dfu_work *dw)
+{
+ struct f_dfu *dfu = dw->dfu;
+
+ pr_debug("do open upload\n");
+
+ dfufd = open(dfu_file_entry->filename, O_RDONLY);
+ if (dfufd < 0) {
+ perror("open");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errFILE;
+ }
+}
+
+static void dfu_do_close(struct dfu_work *dw)
+{
+ struct stat s;
+
+ pr_debug("do close\n");
+
+ if (dfufd > 0) {
+ close(dfufd);
+ dfufd = -EINVAL;
+ }
+
+ if (!stat(DFU_TEMPFILE, &s))
+ unlink(DFU_TEMPFILE);
+
+ dw->dfu->dfu_state = DFU_STATE_dfuIDLE;
+}
+
+static void dfu_do_copy(struct dfu_work *dw)
+{
+ struct f_dfu *dfu = dw->dfu;
+ unsigned flags = O_WRONLY;
+ int ret, fd;
+
+ pr_debug("do copy\n");
+
+ if (dfu_file_entry->flags & FILE_LIST_FLAG_CREATE)
+ flags |= O_CREAT | O_TRUNC;
+
+ fd = open(dfu_file_entry->filename, flags);
+ if (fd < 0) {
+ perror("open");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errERASE;
+ return;
+ }
+
+ ret = erase(fd, ERASE_SIZE_ALL, 0);
+ close(fd);
+ if (ret && ret != -ENOSYS) {
+ perror("erase");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errERASE;
+ return;
+ }
+
+ ret = copy_file(DFU_TEMPFILE, dfu_file_entry->filename, 0);
+ if (ret) {
+ pr_err("copy file failed\n");
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ dfu->dfu_status = DFU_STATUS_errWRITE;
+ return;
+ }
+
+ dfu->dfu_state = DFU_STATE_dfuIDLE;
+}
+
+static int
+dfu_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct usb_descriptor_header **header;
+ struct usb_interface_descriptor *desc;
+ struct file_list_entry *fentry;
+ struct f_dfu *dfu = func_to_dfu(f);
+ int i;
+ int status;
+ struct usb_string *us;
+
+ if (!dfu_files) {
+ const struct usb_function_instance *fi = f->fi;
+ struct f_dfu_opts *opts = container_of(fi, struct f_dfu_opts, func_inst);
+
+ dfu_files = opts->files;
+ }
+
+ dfu_string_defs = xzalloc(sizeof(struct usb_string) * (dfu_files->num_entries + 2));
+ dfu_string_defs[0].s = "Generic DFU";
+ i = 0;
+ file_list_for_each_entry(dfu_files, fentry) {
+ dfu_string_defs[i + 1].s = fentry->name;
+ i++;
+ }
+
+ dfu_string_defs[i + 1].s = NULL;
+ dfu_string_table.strings = dfu_string_defs;
+
+ dfu->dfu_state = DFU_STATE_dfuIDLE;
+ dfu->dfu_status = DFU_STATUS_OK;
+
+ dfu->dnreq = usb_ep_alloc_request(c->cdev->gadget->ep0);
+ if (!dfu->dnreq) {
+ pr_err("usb_ep_alloc_request failed\n");
+ status = -ENOMEM;
+ goto out;
+ }
+ dfu->dnreq->buf = dma_alloc(CONFIG_USBD_DFU_XFER_SIZE);
+ dfu->dnreq->complete = dn_complete;
+ dfu->dnreq->zero = 0;
+
+ us = usb_gstrings_attach(cdev, dfu_strings, dfu_files->num_entries + 1);
+ if (IS_ERR(us)) {
+ status = PTR_ERR(us);
+ goto out;
+ }
+
+ dfu->wq.fn = dfu_do_work;
+ dfu->wq.cancel = dfu_work_cancel;
+ wq_register(&dfu->wq);
+
+ /* allocate instance-specific interface IDs, and patch descriptors */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto out;
+
+ header = xzalloc(sizeof(void *) * (dfu_files->num_entries + 2));
+ desc = xzalloc(sizeof(struct usb_interface_descriptor) * dfu_files->num_entries);
+ for (i = 0; i < dfu_files->num_entries; i++) {
+ desc[i].bLength = USB_DT_INTERFACE_SIZE;
+ desc[i].bDescriptorType = USB_DT_INTERFACE;
+ desc[i].bNumEndpoints = 0;
+ desc[i].bInterfaceClass = 0xfe;
+ desc[i].bInterfaceSubClass = 1;
+ desc[i].bInterfaceProtocol = 2;
+ desc[i].bAlternateSetting = i;
+ desc[i].iInterface = us[i + 1].id;
+ header[i] = (struct usb_descriptor_header *)&desc[i];
+ }
+ header[i] = (struct usb_descriptor_header *) &usb_dfu_func;
+ header[i + 1] = NULL;
+
+ status = usb_assign_descriptors(f, header, header, NULL);
+
+ free(desc);
+ free(header);
+
+ if (status)
+ goto out;
+
+ i = 0;
+ file_list_for_each_entry(dfu_files, fentry) {
+ pr_info("register alt%d(%s) with device %s\n", i, fentry->name, fentry->filename);
+ i++;
+ }
+
+ return 0;
+out:
+ free(dfu_string_defs);
+
+ if (status)
+ ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+ return status;
+}
+
+static void
+dfu_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+
+ dfu_files = NULL;
+ dfu_file_entry = NULL;
+ dfudetach = 0;
+
+ wq_unregister(&dfu->wq);
+
+ usb_free_all_descriptors(f);
+
+ dma_free(dfu->dnreq->buf);
+ usb_ep_free_request(c->cdev->gadget->ep0, dfu->dnreq);
+}
+
+static int dfu_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct file_list_entry *fentry;
+ int i = 0;
+
+ file_list_for_each_entry(dfu_files, fentry) {
+ if (i == alt) {
+ dfu_file_entry = fentry;
+ return 0;
+ }
+
+ i++;
+ }
+
+ return -EINVAL;
+}
+
+static int dfu_status(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ struct dfu_status *dstat = (struct dfu_status *) req->buf;
+
+ dstat->bStatus = dfu->dfu_status;
+ dstat->bState = dfu->dfu_state;
+ dstat->iString = 0;
+ dstat->bwPollTimeout[0] = 10;
+ dstat->bwPollTimeout[1] = 0;
+ dstat->bwPollTimeout[2] = 0;
+
+ return sizeof(*dstat);
+}
+
+static void dfu_cleanup(struct f_dfu *dfu)
+{
+ struct dfu_work *dw;
+
+ pr_debug("dfu cleanup\n");
+
+ memset(&dfu_mtdinfo, 0, sizeof(dfu_mtdinfo));
+ dfu_written = 0;
+ dfu_erased = 0;
+ prog_erase = 0;
+
+ dfu->dfu_state = DFU_STATE_dfuIDLE;
+ dfu->dfu_status = DFU_STATUS_OK;
+
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_close;
+ wq_queue_work(&dfu->wq, &dw->work);
+}
+
+static void dn_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_dfu *dfu = req->context;
+ struct dfu_work *dw;
+
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_write;
+ dw->len = min_t(unsigned int, req->length, CONFIG_USBD_DFU_XFER_SIZE);
+ memcpy(dw->wbuf, req->buf, dw->len);
+ wq_queue_work(&dfu->wq, &dw->work);
+}
+
+static int handle_manifest(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+ struct dfu_work *dw;
+
+ if (dfu_file_entry->flags & FILE_LIST_FLAG_SAFE) {
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_copy;
+ wq_queue_work(&dfu->wq, &dw->work);
+ }
+
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_close;
+ wq_queue_work(&dfu->wq, &dw->work);
+
+ return 0;
+}
+
+static int handle_dnload(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (w_length == 0) {
+ handle_manifest(f, ctrl);
+ dfu->dfu_state = DFU_STATE_dfuMANIFEST;
+ return 0;
+ }
+
+ dfu->dnreq->length = w_length;
+ dfu->dnreq->context = dfu;
+ usb_ep_queue(cdev->gadget->ep0, dfu->dnreq);
+
+ return 0;
+}
+
+static void up_complete(struct usb_ep *ep, struct usb_request *req)
+{
+}
+
+static int handle_upload(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+ struct dfu_work *dw;
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_read;
+ dw->len = w_length;
+ dw->rbuf = dfu->dnreq->buf;
+ wq_queue_work(&dfu->wq, &dw->work);
+
+ return 0;
+}
+
+static void dfu_abort(struct f_dfu *dfu)
+{
+ wq_cancel_work(&dfu->wq);
+
+ dfu_cleanup(dfu);
+}
+
+static int dfu_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ int w_length = le16_to_cpu(ctrl->wLength);
+ int w_value = le16_to_cpu(ctrl->wValue);
+ struct dfu_work *dw;
+
+ if (ctrl->bRequestType == USB_DIR_IN && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == 0x21) {
+ value = min(w_length, (int)sizeof(usb_dfu_func));
+ memcpy(req->buf, &usb_dfu_func, value);
+ goto out;
+ }
+
+ switch (dfu->dfu_state) {
+ case DFU_STATE_dfuIDLE:
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ case USB_REQ_DFU_DNLOAD:
+ if (w_length == 0) {
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ goto out;
+ }
+ pr_debug("starting download to %s\n", dfu_file_entry->filename);
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_open_dnload;
+ wq_queue_work(&dfu->wq, &dw->work);
+
+ value = handle_dnload(f, ctrl);
+ dfu->dfu_state = DFU_STATE_dfuDNLOAD_IDLE;
+ return 0;
+ case USB_REQ_DFU_UPLOAD:
+ dfu->dfu_state = DFU_STATE_dfuUPLOAD_IDLE;
+ pr_debug("starting upload from %s\n", dfu_file_entry->filename);
+ if (!(dfu_file_entry->flags & FILE_LIST_FLAG_READBACK)) {
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ goto out;
+ }
+
+ dw = xzalloc(sizeof(*dw));
+ dw->dfu = dfu;
+ dw->task = dfu_do_open_upload;
+ wq_queue_work(&dfu->wq, &dw->work);
+
+ handle_upload(f, ctrl);
+ return 0;
+ case USB_REQ_DFU_ABORT:
+ dfu->dfu_status = DFU_STATUS_OK;
+ value = 0;
+ break;
+ case USB_REQ_DFU_DETACH:
+ value = 0;
+ dfudetach = 1;
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuDNLOAD_IDLE:
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ case USB_REQ_DFU_DNLOAD:
+ value = handle_dnload(f, ctrl);
+ if (dfu->dfu_state == DFU_STATE_dfuDNLOAD_IDLE) {
+ return 0;
+ }
+ break;
+ case USB_REQ_DFU_ABORT:
+ dfu_abort(dfu);
+ value = 0;
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuUPLOAD_IDLE:
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ case USB_REQ_DFU_UPLOAD:
+ handle_upload(f, ctrl);
+ return 0;
+ case USB_REQ_DFU_ABORT:
+ dfu_abort(dfu);
+ value = 0;
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuERROR:
+ wq_cancel_work(&dfu->wq);
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ case USB_REQ_DFU_CLRSTATUS:
+ dfu_abort(dfu);
+ /* no zlp? */
+ value = 0;
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuMANIFEST_SYNC:
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ dfu->dfu_state = DFU_STATE_dfuMANIFEST;
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuMANIFEST:
+ dfu->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ value = dfu_status(f, ctrl);
+ value = min(value, w_length);
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ *(u8 *)req->buf = dfu->dfu_state;
+ value = sizeof(u8);
+ break;
+ default:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ }
+ break;
+ case DFU_STATE_dfuDNLOAD_SYNC:
+ case DFU_STATE_dfuDNBUSY:
+ dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = -EINVAL;
+ break;
+ default:
+ break;
+ }
+out:
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ req->zero = 0;
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, req);
+ if (value < 0)
+ ERROR(cdev, "dfu response on ttyGS%d, err %d\n",
+ dfu->port_num, value);
+ }
+
+ return value;
+}
+
+static void dfu_disable(struct usb_function *f)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+
+ dfu_abort(dfu);
+}
+
+int usb_dfu_detached(void)
+{
+ return dfudetach;
+}
+
+static void dfu_free_func(struct usb_function *f)
+{
+ struct f_dfu *dfu = func_to_dfu(f);
+
+ free(dfu);
+}
+
+static struct usb_function *dfu_alloc_func(struct usb_function_instance *fi)
+{
+ struct f_dfu *dfu;
+
+ dfu = xzalloc(sizeof(*dfu));
+
+ dfu->func.name = "dfu";
+ dfu->func.strings = dfu_strings;
+ /* descriptors are per-instance copies */
+ dfu->func.bind = dfu_bind;
+ dfu->func.set_alt = dfu_set_alt;
+ dfu->func.setup = dfu_setup;
+ dfu->func.disable = dfu_disable;
+ dfu->func.unbind = dfu_unbind;
+ dfu->func.free_func = dfu_free_func;
+
+ return &dfu->func;
+}
+
+static void dfu_free_instance(struct usb_function_instance *fi)
+{
+ struct f_dfu_opts *opts;
+
+ opts = container_of(fi, struct f_dfu_opts, func_inst);
+ kfree(opts);
+}
+
+static struct usb_function_instance *dfu_alloc_instance(void)
+{
+ struct f_dfu_opts *opts;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+ opts->func_inst.free_func_inst = dfu_free_instance;
+
+ return &opts->func_inst;
+}
+
+DECLARE_USB_FUNCTION_INIT(dfu, dfu_alloc_instance, dfu_alloc_func);