diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index caae4625a1f1ff9013fe7680dfe50f232a48a1ae..53f14c82ff2e47017f9179159a682a7b97422a63 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -667,7 +667,7 @@ void usb_hcd_poll_rh_status(struct usb_hcd *hcd) unsigned long flags; char buffer[6]; /* Any root hubs with > 31 ports? */ - if (unlikely(!hcd->rh_registered)) + if (unlikely(!hcd->rh_pollable)) return; if (!hcd->uses_new_polling && !hcd->status_urb) return; @@ -2217,6 +2217,7 @@ int usb_add_hcd(struct usb_hcd *hcd, retval = -ENOMEM; goto err_allocate_root_hub; } + hcd->self.root_hub = rhdev; switch (hcd->driver->flags & HCD_MASK) { case HCD_USB11: @@ -2231,7 +2232,6 @@ int usb_add_hcd(struct usb_hcd *hcd, default: goto err_set_rh_speed; } - hcd->self.root_hub = rhdev; /* wakeup flag init defaults to "everything works" for root hubs, * but drivers can override it in reset() if needed, along with @@ -2246,6 +2246,7 @@ int usb_add_hcd(struct usb_hcd *hcd, dev_err(hcd->self.controller, "can't setup\n"); goto err_hcd_driver_setup; } + hcd->rh_pollable = 1; /* NOTE: root hub and controller capabilities may not be the same */ if (device_can_wakeup(hcd->self.controller) @@ -2315,9 +2316,12 @@ int usb_add_hcd(struct usb_hcd *hcd, cancel_work_sync(&hcd->wakeup_work); #endif mutex_lock(&usb_bus_list_lock); - usb_disconnect(&hcd->self.root_hub); + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ mutex_unlock(&usb_bus_list_lock); err_register_root_hub: + hcd->rh_pollable = 0; + hcd->poll_rh = 0; + del_timer_sync(&hcd->rh_timer); hcd->driver->stop(hcd); hcd->state = HC_STATE_HALT; hcd->poll_rh = 0; @@ -2328,8 +2332,7 @@ int usb_add_hcd(struct usb_hcd *hcd, err_request_irq: err_hcd_driver_setup: err_set_rh_speed: - hcd->self.root_hub = NULL; - usb_put_dev(rhdev); + usb_put_dev(hcd->self.root_hub); err_allocate_root_hub: usb_deregister_bus(&hcd->self); err_register_bus: @@ -2348,9 +2351,12 @@ EXPORT_SYMBOL_GPL(usb_add_hcd); */ void usb_remove_hcd(struct usb_hcd *hcd) { + struct usb_device *rhdev = hcd->self.root_hub; + dev_info(hcd->self.controller, "remove, state %x\n", hcd->state); - sysfs_remove_group(&hcd->self.root_hub->dev.kobj, &usb_bus_attr_group); + usb_get_dev(rhdev); + sysfs_remove_group(&rhdev->dev.kobj, &usb_bus_attr_group); if (HC_IS_RUNNING (hcd->state)) hcd->state = HC_STATE_QUIESCING; @@ -2365,17 +2371,29 @@ void usb_remove_hcd(struct usb_hcd *hcd) #endif mutex_lock(&usb_bus_list_lock); - usb_disconnect(&hcd->self.root_hub); + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ mutex_unlock(&usb_bus_list_lock); + /* Prevent any more root-hub status calls from the timer. + * The HCD might still restart the timer (if a port status change + * interrupt occurs), but usb_hcd_poll_rh_status() won't invoke + * the hub_status_data() callback. + */ + hcd->rh_pollable = 0; + hcd->poll_rh = 0; + del_timer_sync(&hcd->rh_timer); + hcd->driver->stop(hcd); hcd->state = HC_STATE_HALT; + /* In case the HCD restarted the timer, stop it again. */ hcd->poll_rh = 0; del_timer_sync(&hcd->rh_timer); if (hcd->irq >= 0) free_irq(hcd->irq, hcd); + + usb_put_dev(hcd->self.root_hub); usb_deregister_bus(&hcd->self); hcd_buffer_destroy(hcd); } diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 2e3a4ea1a3da77fa39522e022bd4aff9e98059be..11b638195901450cda66f9a99b26e8d9ca0cf00d 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -95,6 +95,7 @@ struct usb_hcd { #define HCD_FLAG_SAW_IRQ 0x00000002 unsigned rh_registered:1;/* is root hub registered? */ + unsigned rh_pollable:1; /* may we poll the root hub? */ /* The next flag is a stopgap, to be removed when all the HCDs * support the new root-hub polling mechanism. */