It turns out that adding basic IO support to "gadgetfs" is
actually pretty simple.  It added only about 1 KByte to the
size of the compiled module: basically just turning each
kiocb into a usb_request, issuing it, and turning completion
callbacks into aio_complete() calls.   There were minor
complications for copy_to_user() and cancelation.

A patch is attached, and it's been lightly tested with the
updated example at http://www.linux-usb.org/gadget/usb.c
which shows how the userspace API looks (find #ifdef AIO).

I think this should be of general interest to the Linux-USB
community as a preview of the sorts of things that can be
done when we move away from the "usbfs" multiplexed I/O
model ... all those usb-specific ioctls for async I/O can
be replaced with standard kernel APIs that need no special
documentation or userspace support.

Anyway, the patch is attached. Comments, anyone?

- Dave

--- 1.6/drivers/usb/gadget/inode.c      Mon Oct 27 19:42:59 2003
+++ edited/drivers/usb/gadget/inode.c   Tue Nov 18 08:09:13 2003
@@ -20,7 +20,7 @@
  */
 
 
-#define        DEBUG 1                 /* data to help fault diagnosis */
+// #define     DEBUG                   /* data to help fault diagnosis */
 // #define     VERBOSE         /* extra debug messages (success too) */
 
 #include <linux/init.h>
@@ -44,7 +44,8 @@
  * The gadgetfs API maps each endpoint to a file descriptor so that you
  * can use standard synchronous read/write calls for I/O.  There's some
  * O_NONBLOCK and O_ASYNC/FASYNC style i/o support.  Example usermode
- * drivers show how this works in practice.
+ * drivers show how this works in practice.  You can also use AIO to
+ * eliminate I/O gaps between requests, to help when streaming data.
  *
  * Key parts that must be USB-specific are protocols defining how the
  * read/write operations relate to the hardware state machines.  There
@@ -70,7 +71,7 @@
  */
 
 #define        DRIVER_DESC     "USB Gadget filesystem"
-#define        DRIVER_VERSION  "20 Aug 2003"
+#define        DRIVER_VERSION  "18 Nov 2003"
 
 static const char driver_desc [] = DRIVER_DESC;
 static const char shortname [] = "gadgetfs";
@@ -401,7 +402,7 @@
 
 /* handle a synchronous OUT bulk/intr/iso transfer */
 static ssize_t
-ep_read (struct file *fd, char *buf, size_t len, loff_t *ptr)
+ep_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr)
 {
        struct ep_data          *data = fd->private_data;
        void                    *kbuf;
@@ -445,7 +446,7 @@
 
 /* handle a synchronous IN bulk/intr/iso transfer */
 static ssize_t
-ep_write (struct file *fd, const char *buf, size_t len, loff_t *ptr)
+ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
 {
        struct ep_data          *data = fd->private_data;
        void                    *kbuf;
@@ -534,6 +535,177 @@
        return status;
 }
 
+/*----------------------------------------------------------------------*/
+
+/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */
+
+struct kiocb_priv {
+       struct usb_request      *req;
+       struct ep_data          *epdata;
+       void                    *buf;
+       char __user             *ubuf;
+       unsigned                actual;
+};
+
+static int ep_aio_cancel(struct kiocb *iocb, struct io_event *e)
+{
+       struct kiocb_priv       *priv = (void *) &iocb->private;
+       struct ep_data          *epdata;
+       int                     value;
+
+       local_irq_disable();
+       epdata = priv->epdata;
+       // spin_lock(&epdata->dev->lock);
+       kiocbSetCancelled(iocb);
+       if (likely(epdata && epdata->ep && priv->req))
+               value = usb_ep_dequeue (epdata->ep, priv->req);
+       else
+               value = -EINVAL;
+       // spin_unlock(&epdata->dev->lock);
+       local_irq_enable();
+
+       aio_put_req(iocb);
+       return value;
+}
+
+static long ep_aio_read_retry(struct kiocb *iocb)
+{
+       struct kiocb_priv       *priv = (void *) &iocb->private;
+       int                     status = priv->actual;
+
+       /* we "retry" to get the right mm context for this: */
+       status = copy_to_user(priv->ubuf, priv->buf, priv->actual);
+       if (unlikely(0 != status))
+               status = -EFAULT;
+       else
+               status = priv->actual;
+       kfree(priv->buf);
+       aio_put_req(iocb);
+       return status;
+}
+
+static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct kiocb            *iocb = req->context;
+       struct kiocb_priv       *priv = (void *) &iocb->private;
+       struct ep_data          *epdata = priv->epdata;
+
+       /* lock against disconnect (and ideally, cancel) */
+       spin_lock(&epdata->dev->lock);
+       priv->req = 0;
+       priv->epdata = 0;
+       if (NULL == iocb->ki_retry
+                       || unlikely(0 == req->actual)
+                       || unlikely(kiocbIsCancelled(iocb))) {
+               kfree(req->buf);
+               /* aio_complete() reports bytes-transferred _and_ faults */
+               if (unlikely(kiocbIsCancelled(iocb)))
+                       aio_put_req(iocb);
+               else
+                       aio_complete(iocb,
+                               req->actual ? req->actual : req->status,
+                               req->status);
+       } else {
+               /* retry() won't report both; so we hide some faults */
+               if (unlikely(0 != req->status))
+                       DBG(epdata->dev, "%s fault %d len %d\n",
+                               ep->name, req->status, req->actual);
+
+               priv->buf = req->buf;
+               priv->actual = req->actual;
+               kick_iocb(iocb);
+       }
+       spin_unlock(&epdata->dev->lock);
+
+       usb_ep_free_request(ep, req);
+       put_ep(epdata);
+}
+
+static ssize_t
+ep_aio_rwtail(struct kiocb *iocb, char *buf, size_t len, struct ep_data *epdata)
+{
+       struct kiocb_priv       *priv = (void *) &iocb->private;
+       struct usb_request      *req;
+       ssize_t                 value;
+
+       value = get_ready_ep(iocb->ki_filp->f_flags, epdata);
+       if (unlikely(value < 0)) {
+               kfree(buf);
+               return value;
+       }
+
+       iocb->ki_cancel = ep_aio_cancel;
+       get_ep(epdata);
+       priv->epdata = epdata;
+       priv->actual = 0;
+
+       /* each kiocb is coupled to one usb_request, but we can't
+        * allocate or submit those if the host disconnected.
+        */
+       spin_lock_irq(&epdata->dev->lock);
+       if (likely(epdata->ep)) {
+               req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC);
+               if (likely(req)) {
+                       priv->req = req;
+                       req->buf = buf;
+                       req->length = len;
+                       req->complete = ep_aio_complete;
+                       req->context = iocb;
+                       value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC);
+                       if (unlikely(0 != value))
+                               usb_ep_free_request(epdata->ep, req);
+               } else
+                       value = -EAGAIN;
+       } else
+               value = -ENODEV;
+       spin_unlock_irq(&epdata->dev->lock);
+
+       up(&epdata->lock);
+
+       if (unlikely(value))
+               put_ep(epdata);
+       else
+               value = -EIOCBQUEUED;
+       return value;
+}
+
+static ssize_t
+ep_aio_read(struct kiocb *iocb, char __user *ubuf, size_t len, loff_t o)
+{
+       struct kiocb_priv       *priv = (void *) &iocb->private;
+       struct ep_data          *epdata = iocb->ki_filp->private_data;
+       char                    *buf;
+
+       if (unlikely(epdata->desc.bEndpointAddress & USB_DIR_IN))
+               return -EINVAL;
+       buf = kmalloc(len, GFP_KERNEL);
+       if (unlikely(!buf))
+               return -ENOMEM;
+       iocb->ki_retry = ep_aio_read_retry;
+       priv->ubuf = ubuf;
+       return ep_aio_rwtail(iocb, buf, len, epdata);
+}
+
+static ssize_t
+ep_aio_write(struct kiocb *iocb, const char __user *ubuf, size_t len, loff_t o)
+{
+       struct ep_data          *epdata = iocb->ki_filp->private_data;
+       char                    *buf;
+
+       if (unlikely(!(epdata->desc.bEndpointAddress & USB_DIR_IN)))
+               return -EINVAL;
+       buf = kmalloc(len, GFP_KERNEL);
+       if (unlikely(!buf))
+               return -ENOMEM;
+       if (unlikely(copy_from_user(buf, ubuf, len) != 0)) {
+               kfree(buf);
+               return -EFAULT;
+       }
+       return ep_aio_rwtail(iocb, buf, len, epdata);
+}
+
+/*----------------------------------------------------------------------*/
+
 /* used after endpoint configuration */
 static struct file_operations ep_io_operations = {
        .owner =        THIS_MODULE,
@@ -542,8 +714,8 @@
        .ioctl =        ep_ioctl,
        .release =      ep_release,
 
-       // .aio_read =  ep_aio_read,
-       // .aio_write = ep_aio_write,
+       .aio_read =     ep_aio_read,
+       .aio_write =    ep_aio_write,
 };
 
 /* ENDPOINT INITIALIZATION

Reply via email to