File system operation shouldn't be executed in a poller. Use
a workqueue to delay filesystem operation to command context.

This is an RFC, extra work must be done to properly handle error
cases and dfu cleanup.

Signed-off-by: Jules Maselbas <[email protected]>
---
 drivers/usb/gadget/dfu.c | 321 ++++++++++++++++++++++++++-------------
 1 file changed, 216 insertions(+), 105 deletions(-)

diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c
index 9d6a9d252..75abd1576 100644
--- a/drivers/usb/gadget/dfu.c
+++ b/drivers/usb/gadget/dfu.c
@@ -54,6 +54,7 @@
 #include <fs.h>
 #include <ioctl.h>
 #include <linux/mtd/mtd-abi.h>
+#include <work.h>
 
 #define USB_DT_DFU                     0x21
 
@@ -153,6 +154,7 @@ struct f_dfu {
        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)
@@ -173,6 +175,178 @@ static struct usb_gadget_strings *dfu_strings[] = {
 };
 
 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);
+
+       /* TODO: find a better way to skip tasks when the dfu gadget
+        * has encounter an error and dfu_cleanup has been called */
+       if (dw->task && dw->dfu->dfu_status == DFU_STATUS_OK)
+               dw->task(dw);
+
+       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;
+       size_t size, wlen = dw->len;
+       int ret;
+
+       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_status = DFU_STATUS_errERASE;
+                       dfu_cleanup(dfu);
+                       return;
+               }
+       }
+
+       dfu_written += wlen;
+       ret = write(dfufd, dw->wbuf, wlen);
+       if (ret < (int)wlen) {
+               perror("write");
+               dfu->dfu_status = DFU_STATUS_errWRITE;
+               dfu_cleanup(dfu);
+       }
+}
+
+static void dfu_do_read(struct dfu_work *dw)
+{
+       struct f_dfu *dfu = dw->dfu;
+       struct usb_composite_dev *cdev = dfu->func.config->cdev;
+       size_t size, rlen = dw->len;
+
+       debug("do read\n");
+
+       size = read(dfufd, dfu->dnreq->buf, rlen);
+       dfu->dnreq->length = size;
+       if (size < (int)rlen) {
+               perror("read");
+               dfu_cleanup(dfu);
+               dfu->dfu_state = DFU_STATE_dfuIDLE;
+       }
+
+       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;
+
+       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_status = DFU_STATUS_errFILE;
+               goto out;
+       }
+
+       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;
+       }
+
+       return;
+out:
+       dfu->dfu_state = DFU_STATE_dfuERROR;
+       dfu_cleanup(dfu);
+}
+
+static void dfu_do_open_upload(struct dfu_work *dw)
+{
+       struct f_dfu *dfu = dw->dfu;
+
+       debug("do open upload\n");
+
+       dfufd = open(dfu_file_entry->filename, O_RDONLY);
+       if (dfufd < 0) {
+               perror("open");
+               dfu->dfu_status = DFU_STATUS_errFILE;
+               dfu->dfu_state = DFU_STATE_dfuERROR;
+               dfu_cleanup(dfu);
+       }
+}
+
+static void dfu_do_copy(struct dfu_work *dw)
+{
+       struct f_dfu *dfu = dw->dfu;
+       unsigned flags = O_WRONLY;
+       int ret, fd;
+
+       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_status = DFU_STATUS_errERASE;
+               goto out;
+       }
+
+       ret = erase(fd, ERASE_SIZE_ALL, 0);
+       close(fd);
+       if (ret && ret != -ENOSYS) {
+               perror("erase");
+               dfu->dfu_status = DFU_STATUS_errERASE;
+               goto out;
+       }
+
+       ret = copy_file(DFU_TEMPFILE, dfu_file_entry->filename, 0);
+       if (ret) {
+               dfu->dfu_status = DFU_STATUS_errWRITE;
+               printf("copy file failed\n");
+               goto out;
+       }
+
+       dfu->dfu_state = DFU_STATE_dfuIDLE;
+       dfu_cleanup(dfu);
+
+       return;
+out:
+       dfu->dfu_state = DFU_STATE_dfuERROR;
+       dfu_cleanup(dfu);
+}
 
 static int
 dfu_bind(struct usb_configuration *c, struct usb_function *f)
@@ -223,6 +397,10 @@ dfu_bind(struct usb_configuration *c, struct usb_function 
*f)
                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)
@@ -278,6 +456,8 @@ dfu_unbind(struct usb_configuration *c, struct usb_function 
*f)
        dfu_file_entry = NULL;
        dfudetach = 0;
 
+       wq_unregister(&dfu->wq);
+
        usb_free_all_descriptors(f);
 
        dma_free(dfu->dnreq->buf);
@@ -327,6 +507,9 @@ static void dfu_cleanup(struct f_dfu *dfu)
        dfu_erased = 0;
        prog_erase = 0;
 
+       /* TODO: Right now, close and stat operation can be called
+        * in a poller, in dfu_abort and dfu_disable. */
+
        if (dfufd > 0) {
                close(dfufd);
                dfufd = -EINVAL;
@@ -339,28 +522,15 @@ static void dfu_cleanup(struct f_dfu *dfu)
 static void dn_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct f_dfu            *dfu = req->context;
-       loff_t size;
-       int ret;
+       struct dfu_work *dw;
 
-       if (prog_erase && (dfu_written + req->length) > dfu_erased) {
-               size = roundup(req->length, dfu_mtdinfo.erasesize);
-               ret = erase(dfufd, size, dfu_erased);
-               dfu_erased += size;
-               if (ret && ret != -ENOSYS) {
-                       perror("erase");
-                       dfu->dfu_status = DFU_STATUS_errERASE;
-                       dfu_cleanup(dfu);
-                       return;
-               }
-       }
+       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);
 
-       dfu_written += req->length;
-       ret = write(dfufd, req->buf, req->length);
-       if (ret < (int)req->length) {
-               perror("write");
-               dfu->dfu_status = DFU_STATUS_errWRITE;
-               dfu_cleanup(dfu);
-       }
+       wq_queue_work(&dfu->wq, &dw->work);
 }
 
 static int handle_dnload(struct usb_function *f, const struct usb_ctrlrequest 
*ctrl)
@@ -370,12 +540,7 @@ static int handle_dnload(struct usb_function *f, const 
struct usb_ctrlrequest *c
        u16                     w_length = le16_to_cpu(ctrl->wLength);
 
        if (w_length == 0) {
-               if (dfu_file_entry->flags & FILE_LIST_FLAG_SAFE) {
-                       dfu->dfu_state = DFU_STATE_dfuMANIFEST;
-               } else {
-                       dfu->dfu_state = DFU_STATE_dfuIDLE;
-                       dfu_cleanup(dfu);
-               }
+               dfu->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
                return 0;
        }
 
@@ -389,48 +554,18 @@ static int handle_dnload(struct usb_function *f, const 
struct usb_ctrlrequest *c
 static int handle_manifest(struct usb_function *f, const struct 
usb_ctrlrequest *ctrl)
 {
        struct f_dfu            *dfu = func_to_dfu(f);
-       int ret;
+       struct dfu_work *dw;
 
        dfu->dfu_state = DFU_STATE_dfuIDLE;
 
        if (dfu_file_entry->flags & FILE_LIST_FLAG_SAFE) {
-               int fd;
-               unsigned flags = O_WRONLY;
-
-               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_status = DFU_STATUS_errERASE;
-                       ret = -EINVAL;
-                       goto out;
-               }
-
-               ret = erase(fd, ERASE_SIZE_ALL, 0);
-               close(fd);
-               if (ret && ret != -ENOSYS) {
-                       dfu->dfu_status = DFU_STATUS_errERASE;
-                       perror("erase");
-                       goto out;
-               }
-
-               ret = copy_file(DFU_TEMPFILE, dfu_file_entry->filename, 0);
-               if (ret) {
-                       printf("copy file failed\n");
-                       ret = -EINVAL;
-                       goto out;
-               }
+               dw = xzalloc(sizeof(*dw));
+               dw->dfu = dfu;
+               dw->task = dfu_do_copy;
+               wq_queue_work(&dfu->wq, &dw->work);
        }
 
        return 0;
-
-out:
-       dfu->dfu_status = DFU_STATUS_errWRITE;
-       dfu->dfu_state = DFU_STATE_dfuERROR;
-       dfu_cleanup(dfu);
-       return ret;
 }
 
 static void up_complete(struct usb_ep *ep, struct usb_request *req)
@@ -440,20 +575,17 @@ 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 usb_composite_dev *cdev = f->config->cdev;
+       struct dfu_work *dw;
        u16                     w_length = le16_to_cpu(ctrl->wLength);
-       int len;
-
-       len = read(dfufd, dfu->dnreq->buf, w_length);
-
-       dfu->dnreq->length = len;
-       if (len < w_length) {
-               dfu_cleanup(dfu);
-               dfu->dfu_state = DFU_STATE_dfuIDLE;
-       }
 
-       dfu->dnreq->complete = up_complete;
-       usb_ep_queue(cdev->gadget->ep0, dfu->dnreq);
+       /* RFC: I didn't found a better way to queue the usb response other
+        * than making dfu_do_read call usb_ep_queue after reading from file */
+       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;
 }
@@ -474,7 +606,7 @@ static int dfu_setup(struct usb_function *f, const struct 
usb_ctrlrequest *ctrl)
        int                     value = -EOPNOTSUPP;
        int                     w_length = le16_to_cpu(ctrl->wLength);
        int                     w_value = le16_to_cpu(ctrl->wValue);
-       int ret;
+       struct dfu_work *dw;
 
        if (ctrl->bRequestType == USB_DIR_IN && ctrl->bRequest == 
USB_REQ_GET_DESCRIPTOR
                        && (w_value >> 8) == 0x21) {
@@ -501,28 +633,10 @@ static int dfu_setup(struct usb_function *f, const struct 
usb_ctrlrequest *ctrl)
                                goto out;
                        }
                        debug("dfu: starting download to %s\n", 
dfu_file_entry->filename);
-                       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) {
-                               dfu->dfu_state = DFU_STATE_dfuERROR;
-                               perror("open");
-                               goto out;
-                       }
-
-                       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;
-                       }
+                       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;
@@ -534,12 +648,12 @@ static int dfu_setup(struct usb_function *f, const struct 
usb_ctrlrequest *ctrl)
                                dfu->dfu_state = DFU_STATE_dfuERROR;
                                goto out;
                        }
-                       dfufd = open(dfu_file_entry->filename, O_RDONLY);
-                       if (dfufd < 0) {
-                               dfu->dfu_state = DFU_STATE_dfuERROR;
-                               perror("open");
-                               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:
@@ -648,9 +762,6 @@ static int dfu_setup(struct usb_function *f, const struct 
usb_ctrlrequest *ctrl)
                break;
        case DFU_STATE_dfuMANIFEST:
                value = handle_manifest(f, ctrl);
-               if (dfu->dfu_state != DFU_STATE_dfuIDLE) {
-                       return 0;
-               }
                switch (ctrl->bRequest) {
                case USB_REQ_DFU_GETSTATUS:
                        value = dfu_status(f, ctrl);
-- 
2.17.1



_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to