summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hcd.c
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2011-03-07 11:11:52 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2011-03-07 12:14:06 -0800
commit9b37596a2e860404503a3f2a6513db60c296bfdc (patch)
treef47f8e38895d0ce9db252328f686482bad79716f /drivers/usb/core/hcd.c
parente4738e29bef8ed9bdd8a0606d0561557b4547649 (diff)
downloadlinux-9b37596a2e860404503a3f2a6513db60c296bfdc.tar.bz2
USB: move usbcore away from hcd->state
The hcd->state variable is a disaster. It's not clearly owned by either usbcore or the host controller drivers, and they both change it from time to time, potentially stepping on each other's toes. It's not protected by any locks. And there's no mechanism to prevent it from going through an invalid transition. This patch (as1451) takes a first step toward fixing these problems. As it turns out, usbcore uses hcd->state for essentially only two things: checking whether the controller's root hub is running and checking whether the controller has died. Therefore the patch adds two new atomic bitflags to the hcd structure, to store these pieces of information. The new flags are used only by usbcore, and a private spinlock prevents invalid combinations (a dead controller's root hub cannot be running). The patch does not change the places where usbcore sets hcd->state, since HCDs may depend on them. Furthermore, there is one place in usb_hcd_irq() where usbcore still must use hcd->state: An HCD's interrupt handler can implicitly indicate that the controller died by setting hcd->state to HC_STATE_HALT. Nevertheless, the new code is a big improvement over the current code. The patch makes one other change. The hcd_bus_suspend() and hcd_bus_resume() routines now check first whether the host controller has died; if it has then they return immediately without calling the HCD's bus_suspend or bus_resume methods. This fixes the major problem reported in Bugzilla #29902: The system fails to suspend after a host controller dies during system resume. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested-by: Alex Terekhov <a.terekhov@gmail.com> CC: <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core/hcd.c')
-rw-r--r--drivers/usb/core/hcd.c55
1 files changed, 41 insertions, 14 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 24765fd6cf12..e7d0c4571bbe 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -983,7 +983,7 @@ static int register_root_hub(struct usb_hcd *hcd)
spin_unlock_irq (&hcd_root_hub_lock);
/* Did the HC die before the root hub was registered? */
- if (hcd->state == HC_STATE_HALT)
+ if (HCD_DEAD(hcd) || hcd->state == HC_STATE_HALT)
usb_hc_died (hcd); /* This time clean up */
}
@@ -1089,13 +1089,10 @@ int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb)
* Check the host controller's state and add the URB to the
* endpoint's queue.
*/
- switch (hcd->state) {
- case HC_STATE_RUNNING:
- case HC_STATE_RESUMING:
+ if (HCD_RH_RUNNING(hcd)) {
urb->unlinked = 0;
list_add_tail(&urb->urb_list, &urb->ep->urb_list);
- break;
- default:
+ } else {
rc = -ESHUTDOWN;
goto done;
}
@@ -1931,7 +1928,7 @@ int usb_hcd_get_frame_number (struct usb_device *udev)
{
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
- if (!HC_IS_RUNNING (hcd->state))
+ if (!HCD_RH_RUNNING(hcd))
return -ESHUTDOWN;
return hcd->driver->get_frame_number (hcd);
}
@@ -1948,9 +1945,15 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg)
dev_dbg(&rhdev->dev, "bus %s%s\n",
(msg.event & PM_EVENT_AUTO ? "auto-" : ""), "suspend");
+ if (HCD_DEAD(hcd)) {
+ dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "suspend");
+ return 0;
+ }
+
if (!hcd->driver->bus_suspend) {
status = -ENOENT;
} else {
+ clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
hcd->state = HC_STATE_QUIESCING;
status = hcd->driver->bus_suspend(hcd);
}
@@ -1958,7 +1961,12 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg)
usb_set_device_state(rhdev, USB_STATE_SUSPENDED);
hcd->state = HC_STATE_SUSPENDED;
} else {
- hcd->state = old_state;
+ spin_lock_irq(&hcd_root_hub_lock);
+ if (!HCD_DEAD(hcd)) {
+ set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
+ hcd->state = old_state;
+ }
+ spin_unlock_irq(&hcd_root_hub_lock);
dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
"suspend", status);
}
@@ -1973,9 +1981,13 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg)
dev_dbg(&rhdev->dev, "usb %s%s\n",
(msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume");
+ if (HCD_DEAD(hcd)) {
+ dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume");
+ return 0;
+ }
if (!hcd->driver->bus_resume)
return -ENOENT;
- if (hcd->state == HC_STATE_RUNNING)
+ if (HCD_RH_RUNNING(hcd))
return 0;
hcd->state = HC_STATE_RESUMING;
@@ -1984,10 +1996,15 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg)
if (status == 0) {
/* TRSMRCY = 10 msec */
msleep(10);
- usb_set_device_state(rhdev, rhdev->actconfig
- ? USB_STATE_CONFIGURED
- : USB_STATE_ADDRESS);
- hcd->state = HC_STATE_RUNNING;
+ spin_lock_irq(&hcd_root_hub_lock);
+ if (!HCD_DEAD(hcd)) {
+ usb_set_device_state(rhdev, rhdev->actconfig
+ ? USB_STATE_CONFIGURED
+ : USB_STATE_ADDRESS);
+ set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
+ hcd->state = HC_STATE_RUNNING;
+ }
+ spin_unlock_irq(&hcd_root_hub_lock);
} else {
hcd->state = old_state;
dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
@@ -2098,7 +2115,7 @@ irqreturn_t usb_hcd_irq (int irq, void *__hcd)
*/
local_irq_save(flags);
- if (unlikely(hcd->state == HC_STATE_HALT || !HCD_HW_ACCESSIBLE(hcd))) {
+ if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd))) {
rc = IRQ_NONE;
} else if (hcd->driver->irq(hcd) == IRQ_NONE) {
rc = IRQ_NONE;
@@ -2132,6 +2149,8 @@ void usb_hc_died (struct usb_hcd *hcd)
dev_err (hcd->self.controller, "HC died; cleaning up\n");
spin_lock_irqsave (&hcd_root_hub_lock, flags);
+ clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
+ set_bit(HCD_FLAG_DEAD, &hcd->flags);
if (hcd->rh_registered) {
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
@@ -2274,6 +2293,12 @@ int usb_add_hcd(struct usb_hcd *hcd,
*/
device_init_wakeup(&rhdev->dev, 1);
+ /* HCD_FLAG_RH_RUNNING doesn't matter until the root hub is
+ * registered. But since the controller can die at any time,
+ * let's initialize the flag before touching the hardware.
+ */
+ set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
+
/* "reset" is misnamed; its role is now one-time init. the controller
* should already have been reset (and boot firmware kicked off etc).
*/
@@ -2341,6 +2366,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
return retval;
error_create_attr_group:
+ clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
if (HC_IS_RUNNING(hcd->state))
hcd->state = HC_STATE_QUIESCING;
spin_lock_irq(&hcd_root_hub_lock);
@@ -2393,6 +2419,7 @@ void usb_remove_hcd(struct usb_hcd *hcd)
usb_get_dev(rhdev);
sysfs_remove_group(&rhdev->dev.kobj, &usb_bus_attr_group);
+ clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
if (HC_IS_RUNNING (hcd->state))
hcd->state = HC_STATE_QUIESCING;