Here is another version of my patch to provide proper synchronous unlinks.  
This one uses a struct completion rather than a spinlock, and it also 
eliminates all the awkwardness of the "splice" mechanism currently in use.

Alan Stern

===== include/linux/usb.h 1.124 vs edited =====
--- 1.124/include/linux/usb.h   Wed Jan 22 12:38:08 2003
+++ edited/include/linux/usb.h  Tue Feb  4 11:55:28 2003
@@ -554,6 +554,7 @@
 #define URB_NO_FSBR            0x0020  /* UHCI-specific */
 #define URB_ZERO_PACKET                0x0040  /* Finish bulk OUTs with short packet 
*/
 #define URB_NO_INTERRUPT       0x0080  /* HINT: no non-error interrupt needed */
+#define URB_ALIVE              0x2000  /* Completion handler not yet called */
 
 struct usb_iso_packet_descriptor {
        unsigned int offset;
@@ -726,6 +727,7 @@
 struct urb
 {
        spinlock_t lock;                /* lock for the URB */
+       struct completion *wakeup;      /* synchronous unlink waiting queue */
        atomic_t count;                 /* reference count of the URB */
        void *hcpriv;                   /* private data for host controller */
        struct list_head urb_list;      /* list pointer to all active urbs */
===== drivers/usb/core/urb.c 1.19 vs edited =====
--- 1.19/drivers/usb/core/urb.c Wed Oct 30 22:55:08 2002
+++ edited/drivers/usb/core/urb.c       Tue Feb  4 12:18:51 2003
@@ -192,9 +192,15 @@
        struct usb_device       *dev;
        struct usb_operations   *op;
        int                     is_out;
+       int                     status;
 
        if (!urb || urb->hcpriv || !urb->complete)
                return -EINVAL;
+       if (urb->transfer_flags & URB_ALIVE) {
+               err ("%s: attempted double submission of urb", __FUNCTION__);
+               // This would be a good place for a stack trace
+               return -EINPROGRESS;
+       }
        if (!(dev = urb->dev) || !dev->bus || dev->devnum <= 0)
                return -ENODEV;
        if (!(op = dev->bus->op) || !op->submit_urb)
@@ -346,7 +352,12 @@
                urb->interval = temp;
        }
 
-       return op->submit_urb (urb, mem_flags);
+       status = op->submit_urb (urb, mem_flags);
+       if (!status) {
+               urb->transfer_flags |= URB_ALIVE;
+               urb->wakeup = NULL;
+       }
+       return status;
 }
 
 /*-------------------------------------------------------------------*/
===== drivers/usb/core/hcd.c 1.90 vs edited =====
--- 1.90/drivers/usb/core/hcd.c Wed Jan 22 13:02:11 2003
+++ edited/drivers/usb/core/hcd.c       Tue Feb  4 12:11:26 2003
@@ -1067,30 +1067,6 @@
 
 /*-------------------------------------------------------------------------*/
 
-struct completion_splice {             // modified urb context:
-       /* did we complete? */
-       struct completion       done;
-
-       /* original urb data */
-       usb_complete_t          complete;
-       void                    *context;
-};
-
-static void unlink_complete (struct urb *urb, struct pt_regs *regs)
-{
-       struct completion_splice        *splice;
-
-       splice = (struct completion_splice *) urb->context;
-
-       /* issue original completion call */
-       urb->complete = splice->complete;
-       urb->context = splice->context;
-       urb->complete (urb, regs);
-
-       /* then let the synchronous unlink call complete */
-       complete (&splice->done);
-}
-
 /*
  * called in any context; note ASYNC_UNLINK restrictions
  *
@@ -1103,8 +1079,9 @@
        struct usb_hcd                  *hcd = 0;
        struct device                   *sys = 0;
        unsigned long                   flags;
-       struct completion_splice        splice;
+       struct completion               wakeup_list;
        int                             retval;
+       int                             just_waiting = 0;
 
        if (!urb)
                return -EINVAL;
@@ -1136,24 +1113,37 @@
                goto done;
        }
 
-       if (!urb->hcpriv) {
+       /* If the URB is not ALIVE, then either the completion routine is
+        * running or else the completion routine has exited and the URB
+        * is now idle.  We can distinguish the second case by seeing if
+        * urb->wakeup is NULL; it is always set while the completion routine
+        * is running, unless the completion routine resubmits the URB (in
+        * which case the URB would now be ALIVE).
+        */
+       if (!(urb->transfer_flags & URB_ALIVE) && !urb->wakeup) {
                retval = -EINVAL;
                goto done;
        }
 
        /* Any status except -EINPROGRESS means something already started to
-        * unlink this URB from the hardware.  So there's no more work to do.
+        * unlink this URB from the hardware.  Likewise, if hcpriv is not
+        * set then the low-level driver is done with this URB.  If the
+        * request is async there's no more work to do.  Otherwise we will
+        * just wait for the completion routine to return.
         *
         * FIXME use better explicit urb state
         */
-       if (urb->status != -EINPROGRESS) {
-               retval = -EBUSY;
-               goto done;
+       if (urb->status != -EINPROGRESS || !urb->hcpriv) {
+               if (urb->transfer_flags & URB_ASYNC_UNLINK) {
+                       retval = -EBUSY;
+                       goto done;
+               }
+               just_waiting = 1;
        }
 
        /* maybe set up to block until the urb's completion fires.  the
         * lower level hcd code is always async, locking on urb->status
-        * updates; an intercepted completion unblocks us.
+        * updates; return from the completion routine unblocks us.
         */
        if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
                if (in_interrupt ()) {
@@ -1161,13 +1151,16 @@
                        retval = -EWOULDBLOCK;
                        goto done;
                }
-               /* synchronous unlink: block till we see the completion */
-               init_completion (&splice.done);
-               splice.complete = urb->complete;
-               splice.context = urb->context;
-               urb->complete = unlink_complete;
-               urb->context = &splice;
-               urb->status = -ENOENT;
+               /* synchronous unlink: block till after the completion */
+               if (!urb->wakeup) {
+                       urb->wakeup = &wakeup_list;
+                       init_completion (&wakeup_list);
+               }
+               /* Only indicate that the URB was unlinked if the request
+                * arrived before the URB had finished anyway.
+                */
+               if (!just_waiting)
+                       urb->status = -ENOENT;
        } else {
                /* asynchronous unlink */
                urb->status = -ECONNRESET;
@@ -1175,21 +1168,17 @@
        spin_unlock (&hcd_data_lock);
        spin_unlock_irqrestore (&urb->lock, flags);
 
-       if (urb == (struct urb *) hcd->rh_timer.data) {
+       if (just_waiting)
+               /* no need to unlink; the urb is already finishing up */
+               ;
+       else if (urb == (struct urb *) hcd->rh_timer.data)
                usb_rh_status_dequeue (hcd, urb);
-               retval = 0;
-       } else {
+       else {
                retval = hcd->driver->urb_dequeue (hcd, urb);
 
                /* hcds shouldn't really fail these calls, but... */
                if (retval) {
                        dev_dbg (sys, "dequeue %p --> %d\n", urb, retval);
-                       if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
-                               spin_lock_irqsave (&urb->lock, flags);
-                               urb->complete = splice.complete;
-                               urb->context = splice.context;
-                               spin_unlock_irqrestore (&urb->lock, flags);
-                       }
                        goto bye;
                }
        }
@@ -1198,7 +1187,7 @@
        if (urb->transfer_flags & URB_ASYNC_UNLINK)
                return -EINPROGRESS;
 
-       wait_for_completion (&splice.done);
+       wait_for_completion (urb->wakeup);
        return 0;
 
 done:
@@ -1289,6 +1278,27 @@
  */
 void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)
 {
+       struct completion       wakeup_list;
+       struct completion       *wakeup_save;
+
+       /*
+        * Mark the URB as no longer ALIVE.
+        *
+        * If there aren't already any processes waiting for the completion
+        * handler, start a new urb->wakeup queue.  Synchronous unlink requests
+        * arriving while the completion handler runs will wait on this queue.
+        * If the handler resubmits the URB then a new queue will be created,
+        * and we will only wake up the processes waiting on _this_ queue.
+        */
+       spin_lock (&urb->lock);
+       urb->transfer_flags &= ~URB_ALIVE;
+       if (!urb->wakeup) {
+               urb->wakeup = &wakeup_list;
+               init_completion (&wakeup_list);
+       }
+       wakeup_save = urb->wakeup;
+       spin_unlock (&urb->lock);
+
        urb_unlink (urb);
 
        // NOTE:  a generic device/urb monitoring hook would go here.
@@ -1311,6 +1321,18 @@
 
        /* pass ownership to the completion handler */
        urb->complete (urb, regs);
+
+       /*
+        * If the urb->wakeup pointer hasn't changed then the URB wasn't
+        * resubmitted, so we must clear urb->wakeup to indicate that the
+        * URB is now idle.
+        */
+       spin_lock (&urb->lock);
+       if (urb->wakeup == wakeup_save)
+               urb->wakeup = NULL;
+       spin_unlock (&urb->lock);
+
+       complete_all (wakeup_save);
        usb_put_urb (urb);
 }
 EXPORT_SYMBOL (usb_hcd_giveback_urb);



-------------------------------------------------------
This SF.NET email is sponsored by:
SourceForge Enterprise Edition + IBM + LinuxWorld = Something 2 See!
http://www.vasoftware.com
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to