Author: thompsa
Date: Sun Nov  1 21:44:37 2009
New Revision: 198775
URL: http://svn.freebsd.org/changeset/base/198775

Log:
  Fix a corner case where usbd_transfer_drain() can return too early if the
  callback has dropped the mutex, leading to a panic.
  
  Submitted by: HPS
  MFC after:    3 days

Modified:
  head/sys/dev/usb/usb_core.h
  head/sys/dev/usb/usb_transfer.c

Modified: head/sys/dev/usb/usb_core.h
==============================================================================
--- head/sys/dev/usb/usb_core.h Sun Nov  1 21:41:44 2009        (r198774)
+++ head/sys/dev/usb/usb_core.h Sun Nov  1 21:44:37 2009        (r198775)
@@ -112,6 +112,7 @@ struct usb_xfer_flags_int {
        uint8_t curr_dma_set:1;         /* used by USB HC/DC driver */
        uint8_t can_cancel_immed:1;     /* set if USB transfer can be
                                         * cancelled immediately */
+       uint8_t doing_callback:1;       /* set if executing the callback */
 };
 
 /*

Modified: head/sys/dev/usb/usb_transfer.c
==============================================================================
--- head/sys/dev/usb/usb_transfer.c     Sun Nov  1 21:41:44 2009        
(r198774)
+++ head/sys/dev/usb/usb_transfer.c     Sun Nov  1 21:44:37 2009        
(r198775)
@@ -1797,8 +1797,18 @@ usbd_transfer_drain(struct usb_xfer *xfe
 
        usbd_transfer_stop(xfer);
 
-       while (usbd_transfer_pending(xfer)) {
+       while (usbd_transfer_pending(xfer) || 
+           xfer->flags_int.doing_callback) {
+
+               /* 
+                * It is allowed that the callback can drop its
+                * transfer mutex. In that case checking only
+                * "usbd_transfer_pending()" is not enough to tell if
+                * the USB transfer is fully drained. We also need to
+                * check the internal "doing_callback" flag.
+                */
                xfer->flags_int.draining = 1;
+
                /*
                 * Wait until the current outstanding USB
                 * transfer is complete !
@@ -2043,6 +2053,9 @@ usbd_callback_wrapper(struct usb_xfer_qu
        /* get next USB transfer in the queue */
        info->done_q.curr = NULL;
 
+       /* set flag in case of drain */
+       xfer->flags_int.doing_callback = 1;
+
        USB_BUS_UNLOCK(info->bus);
        USB_BUS_LOCK_ASSERT(info->bus, MA_NOTOWNED);
 
@@ -2095,12 +2108,17 @@ usbd_callback_wrapper(struct usb_xfer_qu
        if ((!xfer->flags_int.open) &&
            (xfer->flags_int.started) &&
            (xfer->usb_state == USB_ST_ERROR)) {
+               /* clear flag in case of drain */
+               xfer->flags_int.doing_callback = 0;
                /* try to loop, but not recursivly */
                usb_command_wrapper(&info->done_q, xfer);
                return;
        }
 
 done:
+       /* clear flag in case of drain */
+       xfer->flags_int.doing_callback = 0;
+
        /*
         * Check if we are draining.
         */
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to