On Wed, 22 Jan 2003, Oliver Neukum wrote:

> Am Mittwoch, 22. Januar 2003 22:32 schrieb Alan Stern:
> > Here is my patch to implement proper timing of synchronous unlinks.
> > Basically it just uses a spinlock to cause unlink requests during a
> > completion handler to wait until the handler has returned before carrying
> > out the unlink.  This seems like a pretty small and uninvasive sort of
> > change.
> 
> But sadly, not quite right. In the code path leading to calling the
> completion handler, you are grabbing the lock too late. In that window
> unlinking can fail.

Damn -- you're right.  Okay, here's another version.  This one has the 
following two properties:

        An unlink request while the completion handler is running
        will wait for the handler to finish before doing anything.

        A synchronous unlink doesn't return until the urb's completion
        handler has exited.

I think that does just what is needed.  Unfortunately as a consequence of
this, if a completion handler tries to unlink its urb for any reason
(following a resubmit, for example), the unlink call will spin forever.  
I suggest that drivers try to avoid doing this.

The new patch uses a status bit from the transfer_flags.  I don't think
it's much of a problem here, because the bit is only set during the time
that the urb is owned by the usb core (i.e., set during the call to
usb_submit_urb() and reset before the call to the completion handler).

Alan Stern

===== drivers/usb/core/hcd.c 1.89 vs edited =====
--- 1.89/drivers/usb/core/hcd.c Thu Jan 16 04:29:28 2003
+++ edited/drivers/usb/core/hcd.c       Thu Jan 23 16:40:12 2003
@@ -1102,6 +1102,7 @@
        unsigned long                   flags;
        struct completion_splice        splice;
        int                             retval;
+       int                             just_waiting = 0;
 
        if (!urb)
                return -EINVAL;
@@ -1116,8 +1117,13 @@
         * lock sequence needed for the usb_hcd_giveback_urb() code paths
         * (urb lock, then hcd_data_lock) in case some other CPU is now
         * unlinking it.
+        *
+        * In case the completion handler is already running, we first
+        * acquire urb->handler_lock; this will cause us to wait until
+        * the handler has returned.
         */
-       spin_lock_irqsave (&urb->lock, flags);
+       spin_lock_irqsave (&urb->handler_lock, flags);
+       spin_lock (&urb->lock);
        spin_lock (&hcd_data_lock);
 
        if (!urb->dev || !urb->dev->bus) {
@@ -1139,13 +1145,19 @@
        }
 
        /* 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.  For an async request there's
+        * no more work to do.  For a synchronous request, if the completion
+        * handler has not yet run then we must still wait for it.
         *
         * FIXME use better explicit urb state
         */
        if (urb->status != -EINPROGRESS) {
                retval = -EBUSY;
-               goto done;
+               if (urb->transfer_flags & URB_ASYNC_UNLINK)
+                       goto done;
+               if (!(urb->transfer_flags & URB_NOT_COMPLETED))
+                       goto done;
+               just_waiting = 1;
        }
 
        /* maybe set up to block until the urb's completion fires.  the
@@ -1164,15 +1176,23 @@
                splice.context = urb->context;
                urb->complete = unlink_complete;
                urb->context = &splice;
-               urb->status = -ENOENT;
+               /* If the request arrived after the urb had finished anyway,
+                * don't indicate that it was unlinked -- it wasn't.
+                */
+               if (!just_waiting)
+                       urb->status = -ENOENT;
        } else {
                /* asynchronous unlink */
                urb->status = -ECONNRESET;
        }
        spin_unlock (&hcd_data_lock);
-       spin_unlock_irqrestore (&urb->lock, flags);
+       spin_unlock (&urb->lock);
+       spin_unlock_irqrestore (&urb->handler_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 {
@@ -1196,11 +1216,12 @@
                return -EINPROGRESS;
 
        wait_for_completion (&splice.done);
-       return 0;
+       return retval;
 
 done:
        spin_unlock (&hcd_data_lock);
-       spin_unlock_irqrestore (&urb->lock, flags);
+       spin_unlock (&urb->lock);
+       spin_unlock_irqrestore (&urb->handler_lock, flags);
 bye:
        if (retval && sys)
                dev_dbg (sys, "hcd_unlink_urb %p fail %d\n", urb, retval);
@@ -1307,7 +1328,10 @@
        }
 
        /* pass ownership to the completion handler */
+       spin_lock (&urb->handler_lock);
+       urb->transfer_flags &= ~URB_NOT_COMPLETED;
        urb->complete (urb, regs);
+       spin_unlock (&urb->handler_lock);
        usb_put_urb (urb);
 }
 EXPORT_SYMBOL (usb_hcd_giveback_urb);
===== 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       Thu Jan 23 16:36:13 2003
@@ -44,6 +44,7 @@
        memset(urb, 0, sizeof(*urb));
        urb->count = (atomic_t)ATOMIC_INIT(1);
        spin_lock_init(&urb->lock);
+       spin_lock_init(&urb->handler_lock);
 
        return urb;
 }
@@ -192,9 +193,14 @@
        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_NOT_COMPLETED) {
+               BUG();
+               return -EINPROGRESS;
+       }
        if (!(dev = urb->dev) || !dev->bus || dev->devnum <= 0)
                return -ENODEV;
        if (!(op = dev->bus->op) || !op->submit_urb)
@@ -346,7 +352,10 @@
                urb->interval = temp;
        }
 
-       return op->submit_urb (urb, mem_flags);
+       status = op->submit_urb (urb, mem_flags);
+       if (!status)
+               urb->transfer_flags |= URB_NOT_COMPLETED;
+       return status;
 }
 
 /*-------------------------------------------------------------------*/
===== include/linux/usb.h 1.123 vs edited =====
--- 1.123/include/linux/usb.h   Fri Jan 17 13:21:23 2003
+++ edited/include/linux/usb.h  Thu Jan 23 16:35:51 2003
@@ -555,6 +555,7 @@
 #define URB_ZERO_PACKET                0x0040  /* Finish bulk OUTs with short packet 
*/
 #define URB_NO_INTERRUPT       0x0080  /* HINT: no non-error interrupt needed */
 #define URB_TIMEOUT_KILLED     0x1000  /* only set by HCD! */
+#define URB_NOT_COMPLETED      0x2000  /* only set by HCD! */
 
 struct usb_iso_packet_descriptor {
        unsigned int offset;
@@ -728,6 +729,7 @@
 {
        spinlock_t lock;                /* lock for the URB */
        atomic_t count;                 /* reference count of the URB */
+       spinlock_t handler_lock;        /* lock during completion routine */
        void *hcpriv;                   /* private data for host controller */
        struct list_head urb_list;      /* list pointer to all active urbs */
        struct usb_device *dev;         /* (in) pointer to associated device */



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