[PATCH 2/2] usb: gadget: f_fs: add aio support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds asynchronous I/O support for FunctionFS endpoint files.
It adds ffs_epfile_aio_write() and ffs_epfile_aio_read() functions responsible
for preparing AIO operations.

It also modifies ffs_epfile_io() function, adding aio handling code. Instead
of extending list of parameters of this function, there is new struct
ffs_io_data which contains all information needed to perform I/O operation.
Pointer to this struct replaces "buf" and "len" parameters of ffs_epfile_io()
function. Allocated buffer is freed immediately only after sync operation,
because in async IO it's freed in complete funcion. For each async operation
an USB request is allocated, because it allows to have more than one request
queued on single endpoint.

According to changes in ffs_epfile_io() function, functions ffs_epfile_write()
and ffs_epfile_read() are updated to use new API.

For asynchronous I/O operations there is new request complete function named
ffs_epfile_async_io_complete(), which completes AIO operation, and frees
used memory.

Signed-off-by: Robert Baldyga <r.baldyga@xxxxxxxxxxx>
---
 drivers/usb/gadget/f_fs.c |  259 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 223 insertions(+), 36 deletions(-)

diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c
index c820a47..f9b1de3 100644
--- a/drivers/usb/gadget/f_fs.c
+++ b/drivers/usb/gadget/f_fs.c
@@ -27,6 +27,8 @@
 #include <linux/usb/composite.h>
 #include <linux/usb/functionfs.h>
 
+#include <linux/aio.h>
+#include <linux/mmu_context.h>
 #include <linux/poll.h>
 
 #define FUNCTIONFS_MAGIC	0xa647361 /* Chosen by a honest dice roll ;) */
@@ -343,6 +345,25 @@ static char *ffs_prepare_buffer(const char __user *buf, size_t len)
 	__attribute__((warn_unused_result, nonnull));
 
 
+/*  ffs_io_data structure ***************************************************/
+
+struct ffs_io_data {
+	int aio:1;
+	int read:1;
+	
+	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;
+};
+
 /* Control file aka ep0 *****************************************************/
 
 static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req)
@@ -788,8 +809,51 @@ 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)
+{
+	size_t len = 0;
+	int i = 0;
+	int ret;
+
+	struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, work);
+
+	use_mm(io_data->mm);
+	for (i=0; i < io_data->nr_segs; i++) {
+		ret = copy_to_user(io_data->iovec[i].iov_base,
+				   &io_data->buf[len],
+				   io_data->iovec[i].iov_len);
+		len += io_data->iovec[i].iov_len;
+	}
+	unuse_mm(io_data->mm);
+
+	aio_complete(io_data->kiocb, 0, 0);
+
+	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;
+	struct ffs_ep *ep = _ep->driver_data;
+	ep->status = req->status ? req->status : req->actual;
+
+	if (io_data->read) {
+		INIT_WORK(&io_data->work, ffs_user_copy_worker);
+		schedule_work(&io_data->work);
+	}
+	else {
+		aio_complete(io_data->kiocb, 0, 0);
+		kfree(io_data->buf);
+		kfree(io_data);
+	}
+
+	usb_ep_free_request(_ep, req);
+}
+
+static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
 {
 	struct ffs_epfile *epfile = file->private_data;
 	struct ffs_ep *ep;
@@ -825,25 +889,12 @@ first_try:
 		}
 
 		/* Do we halt? */
-		halt = !read == !epfile->in;
+		halt = !io_data->read == !epfile->in;
 		if (halt && epfile->isoc) {
 			ret = -EINVAL;
 			goto error;
 		}
 
-		/* Allocate & copy */
-		if (!halt && !data) {
-			data = kzalloc(len, GFP_KERNEL);
-			if (unlikely(!data))
-				return -ENOMEM;
-
-			if (!read &&
-			    unlikely(__copy_from_user(data, buf, len))) {
-				ret = -EFAULT;
-				goto error;
-			}
-		}
-
 		/* We will be using request */
 		ret = ffs_mutex_lock(&epfile->mutex,
 				     file->f_flags & O_NONBLOCK);
@@ -869,33 +920,86 @@ first_try:
 		spin_unlock_irq(&epfile->ffs->eps_lock);
 		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   = len;
+		data = kzalloc(io_data->len, GFP_KERNEL);
+		if (unlikely(!data))
+			return -ENOMEM;
+		
+		if(io_data->aio) {
+			size_t len = 0;
+			int i;
+			for (i=0; !io_data->read && i < io_data->nr_segs; i++) {
+				if (unlikely(copy_from_user(&data[len],
+					     io_data->iovec[i].iov_base,
+					     io_data->iovec[i].iov_len) != 0)) {
+					ret = -EFAULT;
+					goto error;
+				}
+				len += io_data->iovec[i].iov_len;
+			}
 
-		ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
+			req = usb_ep_alloc_request(ep->ep, GFP_KERNEL);
+			if(unlikely(!req))
+				goto error;
 
-		spin_unlock_irq(&epfile->ffs->eps_lock);
+			req->buf      = data;
+			req->length   = io_data->len;
 
-		if (unlikely(ret < 0)) {
-			/* nop */
-		} else if (unlikely(wait_for_completion_interruptible(&done))) {
-			ret = -EINTR;
-			usb_ep_dequeue(ep->ep, req);
-		} else {
-			ret = ep->status;
-			if (read && ret > 0 &&
-			    unlikely(copy_to_user(buf, data, ret)))
+			io_data->buf = data;
+			io_data->ep = ep->ep;
+			io_data->req = 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);
+
+			if (!io_data->read &&
+			    unlikely(__copy_from_user(data, io_data->buf,
+			    			      io_data->len))) {
 				ret = -EFAULT;
+				goto error;
+			}
+
+			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 {
+				ret = ep->status;
+				if (io_data->read && ret > 0 &&
+				    unlikely(copy_to_user(io_data->buf, data, ret)))
+					ret = -EFAULT;
+			}
+			kfree(data);
 		}
 	}
 
 	mutex_unlock(&epfile->mutex);
+	return ret;
 error:
 	kfree(data);
 	return ret;
@@ -905,17 +1009,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 = 0;
+	io_data.read = 0;
+	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 = 0;
+	io_data.read = 1;
+	io_data.buf = buf;
+	io_data.len = len;
+
+	return ffs_epfile_io(file, &io_data);
 }
 
 static int
@@ -934,6 +1052,73 @@ 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;
+	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(struct ffs_io_data), GFP_KERNEL);
+	io_data->aio = 1;
+	io_data->read = 0;
+	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(sizeof(struct iovec)*nr_segs, GFP_KERNEL);
+	memcpy(iovec_copy, iovec, sizeof(struct iovec)*nr_segs);
+
+	io_data = kmalloc(sizeof(struct ffs_io_data), GFP_KERNEL);
+	io_data->aio = 1;
+	io_data->read = 1;
+	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)
 {
@@ -990,6 +1175,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,
 };
-- 
1.7.9.5

--
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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux