aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Thompson <thompsa@FreeBSD.org>2009-11-04 21:28:50 +0000
committerAndrew Thompson <thompsa@FreeBSD.org>2009-11-04 21:28:50 +0000
commitfd805728662697d1e229ac07e05f7ed850e59bee (patch)
tree61df86b8ebf21ed8d665aa3b380a840f82e02cb4
parentb9bd7bf67826f0ebde7bb6248101af92474b9858 (diff)
downloadsrc-fd805728662697d1e229ac07e05f7ed850e59bee.tar.gz
src-fd805728662697d1e229ac07e05f7ed850e59bee.zip
MFC r198775
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 Approved by: re (kib)
Notes
Notes: svn path=/releng/8.0/; revision=198930
-rw-r--r--sys/dev/usb/usb_core.h1
-rw-r--r--sys/dev/usb/usb_transfer.c20
2 files changed, 20 insertions, 1 deletions
diff --git a/sys/dev/usb/usb_core.h b/sys/dev/usb/usb_core.h
index 84c163b381c0..a9d273d427f6 100644
--- a/sys/dev/usb/usb_core.h
+++ b/sys/dev/usb/usb_core.h
@@ -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 */
};
/*
diff --git a/sys/dev/usb/usb_transfer.c b/sys/dev/usb/usb_transfer.c
index 8daf475a4b8a..878f64499a50 100644
--- a/sys/dev/usb/usb_transfer.c
+++ b/sys/dev/usb/usb_transfer.c
@@ -1785,8 +1785,18 @@ usbd_transfer_drain(struct usb_xfer *xfer)
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 !
@@ -2031,6 +2041,9 @@ usbd_callback_wrapper(struct usb_xfer_queue *pq)
/* 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);
@@ -2083,12 +2096,17 @@ usbd_callback_wrapper(struct usb_xfer_queue *pq)
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.
*/