Author: hselasky
Date: Sun Oct 27 10:09:53 2013
New Revision: 257206
URL: http://svnweb.freebsd.org/changeset/base/257206

Log:
  Fix a deadlock when trying to power off a USB device. The deadlock
  happens because the code in question is trying to modify the parent
  USB port registers outside the USB explore thread.
  
  MFC after:    3 days

Modified:
  head/sys/dev/usb/usb_dev.c
  head/sys/dev/usb/usb_device.h
  head/sys/dev/usb/usb_generic.c
  head/sys/dev/usb/usb_hub.c

Modified: head/sys/dev/usb/usb_dev.c
==============================================================================
--- head/sys/dev/usb/usb_dev.c  Sun Oct 27 04:59:18 2013        (r257205)
+++ head/sys/dev/usb/usb_dev.c  Sun Oct 27 10:09:53 2013        (r257206)
@@ -1099,7 +1099,7 @@ usb_ioctl(struct cdev *dev, u_long cmd, 
 
        /* Wait for re-enumeration, if any */
 
-       while (f->udev->re_enumerate_wait != 0) {
+       while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) {
 
                usb_unref_device(cpd, &refs);
 

Modified: head/sys/dev/usb/usb_device.h
==============================================================================
--- head/sys/dev/usb/usb_device.h       Sun Oct 27 04:59:18 2013        
(r257205)
+++ head/sys/dev/usb/usb_device.h       Sun Oct 27 10:09:53 2013        
(r257206)
@@ -238,6 +238,9 @@ struct usb_device {
        uint8_t driver_added_refcount;  /* our driver added generation count */
        uint8_t power_mode;             /* see USB_POWER_XXX */
        uint8_t re_enumerate_wait;      /* set if re-enum. is in progress */
+#define        USB_RE_ENUM_DONE        0
+#define        USB_RE_ENUM_START       1
+#define        USB_RE_ENUM_PWR_OFF     2
        uint8_t ifaces_max;             /* number of interfaces present */
        uint8_t endpoints_max;          /* number of endpoints present */
 

Modified: head/sys/dev/usb/usb_generic.c
==============================================================================
--- head/sys/dev/usb/usb_generic.c      Sun Oct 27 04:59:18 2013        
(r257205)
+++ head/sys/dev/usb/usb_generic.c      Sun Oct 27 10:09:53 2013        
(r257206)
@@ -1762,16 +1762,11 @@ ugen_set_power_mode(struct usb_fifo *f, 
 
        switch (mode) {
        case USB_POWER_MODE_OFF:
-               /* get the device unconfigured */
-               err = ugen_set_config(f, USB_UNCONFIG_INDEX);
-               if (err) {
-                       DPRINTFN(0, "Could not unconfigure "
-                           "device (ignored)\n");
+               if (udev->flags.usb_mode == USB_MODE_HOST &&
+                   udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+                       udev->re_enumerate_wait = USB_RE_ENUM_PWR_OFF;
                }
-
-               /* clear port enable */
-               err = usbd_req_clear_port_feature(udev->parent_hub,
-                   NULL, udev->port_no, UHF_PORT_ENABLE);
+               /* set power mode will wake up the explore thread */
                break;
 
        case USB_POWER_MODE_ON:
@@ -1819,9 +1814,9 @@ ugen_set_power_mode(struct usb_fifo *f, 
 
        /* if we are powered off we need to re-enumerate first */
        if (old_mode == USB_POWER_MODE_OFF) {
-               if (udev->flags.usb_mode == USB_MODE_HOST) {
-                       if (udev->re_enumerate_wait == 0)
-                               udev->re_enumerate_wait = 1;
+               if (udev->flags.usb_mode == USB_MODE_HOST &&
+                   udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+                       udev->re_enumerate_wait = USB_RE_ENUM_START;
                }
                /* set power mode will wake up the explore thread */
        }

Modified: head/sys/dev/usb/usb_hub.c
==============================================================================
--- head/sys/dev/usb/usb_hub.c  Sun Oct 27 04:59:18 2013        (r257205)
+++ head/sys/dev/usb/usb_hub.c  Sun Oct 27 10:09:53 2013        (r257206)
@@ -248,7 +248,8 @@ uhub_explore_sub(struct uhub_softc *sc, 
                uint8_t do_unlock;
                
                do_unlock = usbd_enum_lock(child);
-               if (child->re_enumerate_wait) {
+               switch (child->re_enumerate_wait) {
+               case USB_RE_ENUM_START:
                        err = usbd_set_config_index(child,
                            USB_UNCONFIG_INDEX);
                        if (err != 0) {
@@ -263,8 +264,33 @@ uhub_explore_sub(struct uhub_softc *sc, 
                                err = usb_probe_and_attach(child,
                                    USB_IFACE_INDEX_ANY);
                        }
-                       child->re_enumerate_wait = 0;
+                       child->re_enumerate_wait = USB_RE_ENUM_DONE;
                        err = 0;
+                       break;
+
+               case USB_RE_ENUM_PWR_OFF:
+                       /* get the device unconfigured */
+                       err = usbd_set_config_index(child,
+                           USB_UNCONFIG_INDEX);
+                       if (err) {
+                               DPRINTFN(0, "Could not unconfigure "
+                                   "device (ignored)\n");
+                       }
+
+                       /* clear port enable */
+                       err = usbd_req_clear_port_feature(child->parent_hub,
+                           NULL, child->port_no, UHF_PORT_ENABLE);
+                       if (err) {
+                               DPRINTFN(0, "Could not disable port "
+                                   "(ignored)\n");
+                       }
+                       child->re_enumerate_wait = USB_RE_ENUM_DONE;
+                       err = 0;
+                       break;
+
+               default:
+                       child->re_enumerate_wait = USB_RE_ENUM_DONE;
+                       break;
                }
                if (do_unlock)
                        usbd_enum_unlock(child);
@@ -2086,7 +2112,7 @@ usb_peer_should_wakeup(struct usb_device
        return (((udev->power_mode == USB_POWER_MODE_ON) &&
            (udev->flags.usb_mode == USB_MODE_HOST)) ||
            (udev->driver_added_refcount != udev->bus->driver_added_refcount) ||
-           (udev->re_enumerate_wait != 0) ||
+           (udev->re_enumerate_wait != USB_RE_ENUM_DONE) ||
            (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) ||
            (udev->pwr_save.write_refs != 0) ||
            ((udev->pwr_save.read_refs != 0) &&
@@ -2502,6 +2528,8 @@ usbd_set_power_mode(struct usb_device *u
 
 #if USB_HAVE_POWERD
        usb_bus_power_update(udev->bus);
+#else
+       usb_needs_explore(udev->bus, 0 /* no probe */ );
 #endif
 }
 
@@ -2540,8 +2568,8 @@ usbd_filter_power_mode(struct usb_device
 void
 usbd_start_re_enumerate(struct usb_device *udev)
 {
-       if (udev->re_enumerate_wait == 0) {
-               udev->re_enumerate_wait = 1;
+       if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+               udev->re_enumerate_wait = USB_RE_ENUM_START;
                usb_needs_explore(udev->bus, 0);
        }
 }
_______________________________________________
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