diff options
Diffstat (limited to 'drivers/usb/gadget/f_fs.c')
-rw-r--r-- | drivers/usb/gadget/f_fs.c | 265 |
1 files changed, 239 insertions, 26 deletions
diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index 20321631f0f4..1ae741fdace0 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -28,6 +28,8 @@ #include <linux/usb/composite.h> #include <linux/usb/functionfs.h> +#include <linux/aio.h> +#include <linux/mmu_context.h> #include <linux/poll.h> #include "u_fs.h" @@ -158,6 +160,25 @@ struct ffs_epfile { unsigned char _pad; }; +/* ffs_io_data structure ***************************************************/ + +struct ffs_io_data { + bool aio; + bool read; + + struct kiocb *kiocb; + const struct iovec *iovec; + unsigned long nr_segs; + char __user *buf; + size_t len; + + struct mm_struct *mm; + struct work_struct work; + + struct usb_ep *ep; + struct usb_request *req; +}; + static int __must_check ffs_epfiles_create(struct ffs_data *ffs); static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); @@ -635,8 +656,52 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) } } -static ssize_t ffs_epfile_io(struct file *file, - char __user *buf, size_t len, int read) +static void ffs_user_copy_worker(struct work_struct *work) +{ + struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, + work); + int ret = io_data->req->status ? io_data->req->status : + io_data->req->actual; + + if (io_data->read && ret > 0) { + int i; + size_t pos = 0; + use_mm(io_data->mm); + for (i = 0; i < io_data->nr_segs; i++) { + if (unlikely(copy_to_user(io_data->iovec[i].iov_base, + &io_data->buf[pos], + io_data->iovec[i].iov_len))) { + ret = -EFAULT; + break; + } + pos += io_data->iovec[i].iov_len; + } + unuse_mm(io_data->mm); + } + + aio_complete(io_data->kiocb, ret, ret); + + usb_ep_free_request(io_data->ep, io_data->req); + + io_data->kiocb->private = NULL; + if (io_data->read) + kfree(io_data->iovec); + kfree(io_data->buf); + kfree(io_data); +} + +static void ffs_epfile_async_io_complete(struct usb_ep *_ep, + struct usb_request *req) +{ + struct ffs_io_data *io_data = req->context; + + ENTER(); + + INIT_WORK(&io_data->work, ffs_user_copy_worker); + schedule_work(&io_data->work); +} + +static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) { struct ffs_epfile *epfile = file->private_data; struct usb_gadget *gadget = epfile->ffs->gadget; @@ -667,7 +732,7 @@ static ssize_t ffs_epfile_io(struct file *file, } /* Do we halt? */ - halt = !read == !epfile->in; + halt = (!io_data->read == !epfile->in); if (halt && epfile->isoc) { ret = -EINVAL; goto error; @@ -679,15 +744,32 @@ static ssize_t ffs_epfile_io(struct file *file, * Controller may require buffer size to be aligned to * maxpacketsize of an out endpoint. */ - data_len = read ? usb_ep_align_maybe(gadget, ep->ep, len) : len; + data_len = io_data->read ? + usb_ep_align_maybe(gadget, ep->ep, io_data->len) : + io_data->len; data = kmalloc(data_len, GFP_KERNEL); if (unlikely(!data)) return -ENOMEM; - - if (!read && unlikely(copy_from_user(data, buf, len))) { - ret = -EFAULT; - goto error; + if (io_data->aio && !io_data->read) { + int i; + size_t pos = 0; + for (i = 0; i < io_data->nr_segs; i++) { + if (unlikely(copy_from_user(&data[pos], + io_data->iovec[i].iov_base, + io_data->iovec[i].iov_len))) { + ret = -EFAULT; + goto error; + } + pos += io_data->iovec[i].iov_len; + } + } else { + if (!io_data->read && + unlikely(__copy_from_user(data, io_data->buf, + io_data->len))) { + ret = -EFAULT; + goto error; + } } } @@ -710,24 +792,52 @@ static ssize_t ffs_epfile_io(struct file *file, ret = -EBADMSG; } else { /* Fire the request */ - DECLARE_COMPLETION_ONSTACK(done); + struct usb_request *req; - struct usb_request *req = ep->req; - req->context = &done; - req->complete = ffs_epfile_io_complete; - req->buf = data; - req->length = data_len; + if (io_data->aio) { + req = usb_ep_alloc_request(ep->ep, GFP_KERNEL); + if (unlikely(!req)) + goto error; - ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + req->buf = data; + req->length = io_data->len; - spin_unlock_irq(&epfile->ffs->eps_lock); + io_data->buf = data; + io_data->ep = ep->ep; + io_data->req = req; - if (unlikely(ret < 0)) { - /* nop */ - } else if (unlikely(wait_for_completion_interruptible(&done))) { - ret = -EINTR; - usb_ep_dequeue(ep->ep, req); + req->context = io_data; + req->complete = ffs_epfile_async_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + if (unlikely(ret)) { + usb_ep_free_request(ep->ep, req); + goto error; + } + ret = -EIOCBQUEUED; + + spin_unlock_irq(&epfile->ffs->eps_lock); } else { + DECLARE_COMPLETION_ONSTACK(done); + + req = ep->req; + req->buf = data; + req->length = io_data->len; + + req->context = &done; + req->complete = ffs_epfile_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + + spin_unlock_irq(&epfile->ffs->eps_lock); + + if (unlikely(ret < 0)) { + /* nop */ + } else if (unlikely( + wait_for_completion_interruptible(&done))) { + ret = -EINTR; + usb_ep_dequeue(ep->ep, req); + } else { /* * XXX We may end up silently droping data here. * Since data_len (i.e. req->length) may be bigger @@ -736,14 +846,18 @@ static ssize_t ffs_epfile_io(struct file *file, * space for. */ ret = ep->status; - if (read && ret > 0 && - unlikely(copy_to_user(buf, data, - min_t(size_t, ret, len)))) + if (io_data->read && ret > 0 && + unlikely(copy_to_user(io_data->buf, data, + min_t(size_t, ret, + io_data->len)))) ret = -EFAULT; + } + kfree(data); } } mutex_unlock(&epfile->mutex); + return ret; error: kfree(data); return ret; @@ -753,17 +867,31 @@ static ssize_t ffs_epfile_write(struct file *file, const char __user *buf, size_t len, loff_t *ptr) { + struct ffs_io_data io_data; + ENTER(); - return ffs_epfile_io(file, (char __user *)buf, len, 0); + io_data.aio = false; + io_data.read = false; + io_data.buf = (char * __user)buf; + io_data.len = len; + + return ffs_epfile_io(file, &io_data); } static ssize_t ffs_epfile_read(struct file *file, char __user *buf, size_t len, loff_t *ptr) { + struct ffs_io_data io_data; + ENTER(); - return ffs_epfile_io(file, buf, len, 1); + io_data.aio = false; + io_data.read = true; + io_data.buf = buf; + io_data.len = len; + + return ffs_epfile_io(file, &io_data); } static int @@ -782,6 +910,89 @@ ffs_epfile_open(struct inode *inode, struct file *file) return 0; } +static int ffs_aio_cancel(struct kiocb *kiocb) +{ + struct ffs_io_data *io_data = kiocb->private; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + int value; + + ENTER(); + + spin_lock_irq(&epfile->ffs->eps_lock); + + if (likely(io_data && io_data->ep && io_data->req)) + value = usb_ep_dequeue(io_data->ep, io_data->req); + else + value = -EINVAL; + + spin_unlock_irq(&epfile->ffs->eps_lock); + + return value; +} + +static ssize_t ffs_epfile_aio_write(struct kiocb *kiocb, + const struct iovec *iovec, + unsigned long nr_segs, loff_t loff) +{ + struct ffs_io_data *io_data; + + ENTER(); + + io_data = kmalloc(sizeof(*io_data), GFP_KERNEL); + if (unlikely(!io_data)) + return -ENOMEM; + + io_data->aio = true; + io_data->read = false; + io_data->kiocb = kiocb; + io_data->iovec = iovec; + io_data->nr_segs = nr_segs; + io_data->len = kiocb->ki_nbytes; + io_data->mm = current->mm; + + kiocb->private = io_data; + + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + + return ffs_epfile_io(kiocb->ki_filp, io_data); +} + +static ssize_t ffs_epfile_aio_read(struct kiocb *kiocb, + const struct iovec *iovec, + unsigned long nr_segs, loff_t loff) +{ + struct ffs_io_data *io_data; + struct iovec *iovec_copy; + + ENTER(); + + iovec_copy = kmalloc_array(nr_segs, sizeof(*iovec_copy), GFP_KERNEL); + if (unlikely(!iovec_copy)) + return -ENOMEM; + + memcpy(iovec_copy, iovec, sizeof(struct iovec)*nr_segs); + + io_data = kmalloc(sizeof(*io_data), GFP_KERNEL); + if (unlikely(!io_data)) { + kfree(iovec_copy); + return -ENOMEM; + } + + io_data->aio = true; + io_data->read = true; + io_data->kiocb = kiocb; + io_data->iovec = iovec_copy; + io_data->nr_segs = nr_segs; + io_data->len = kiocb->ki_nbytes; + io_data->mm = current->mm; + + kiocb->private = io_data; + + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + + return ffs_epfile_io(kiocb->ki_filp, io_data); +} + static int ffs_epfile_release(struct inode *inode, struct file *file) { @@ -838,6 +1049,8 @@ static const struct file_operations ffs_epfile_operations = { .open = ffs_epfile_open, .write = ffs_epfile_write, .read = ffs_epfile_read, + .aio_write = ffs_epfile_aio_write, + .aio_read = ffs_epfile_aio_read, .release = ffs_epfile_release, .unlocked_ioctl = ffs_epfile_ioctl, }; |