summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2013-10-30 08:05:39 +0000
committerhselasky <hselasky@FreeBSD.org>2013-10-30 08:05:39 +0000
commit9a9c7f1310dd0c292ea36a425791d1f18b4644db (patch)
treeb9220d064a5fe8ced2cbc27dffe864682eaa1173
parent82fad41b08ce33288400daf41003952c88585a31 (diff)
downloadFreeBSD-src-9a9c7f1310dd0c292ea36a425791d1f18b4644db.zip
FreeBSD-src-9a9c7f1310dd0c292ea36a425791d1f18b4644db.tar.gz
MFC r257206:
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. Approved by: re (glebius)
-rw-r--r--sys/dev/usb/usb_dev.c2
-rw-r--r--sys/dev/usb/usb_device.h3
-rw-r--r--sys/dev/usb/usb_generic.c19
-rw-r--r--sys/dev/usb/usb_hub.c38
4 files changed, 44 insertions, 18 deletions
diff --git a/sys/dev/usb/usb_dev.c b/sys/dev/usb/usb_dev.c
index 9bf430b..9e3cef5 100644
--- a/sys/dev/usb/usb_dev.c
+++ b/sys/dev/usb/usb_dev.c
@@ -1099,7 +1099,7 @@ usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread*
/* 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);
diff --git a/sys/dev/usb/usb_device.h b/sys/dev/usb/usb_device.h
index 06b931f..b5b5d13 100644
--- a/sys/dev/usb/usb_device.h
+++ b/sys/dev/usb/usb_device.h
@@ -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 */
diff --git a/sys/dev/usb/usb_generic.c b/sys/dev/usb/usb_generic.c
index 8e1033e..6d8b0f5 100644
--- a/sys/dev/usb/usb_generic.c
+++ b/sys/dev/usb/usb_generic.c
@@ -1762,16 +1762,11 @@ ugen_set_power_mode(struct usb_fifo *f, int mode)
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, int mode)
/* 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 */
}
diff --git a/sys/dev/usb/usb_hub.c b/sys/dev/usb/usb_hub.c
index 46b7d8d..3e9d1cb 100644
--- a/sys/dev/usb/usb_hub.c
+++ b/sys/dev/usb/usb_hub.c
@@ -248,7 +248,8 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
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, struct usb_port *up)
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 *udev)
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 *udev, uint8_t power_mode)
#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 *udev, uint8_t power_mode)
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);
}
}
OpenPOWER on IntegriCloud