diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r-- | drivers/usb/core/hub.c | 45 |
1 files changed, 32 insertions, 13 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5413d71..b38fd67 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -71,6 +71,7 @@ struct usb_hub { unsigned mA_per_port; /* current for each child */ + unsigned init_done:1; unsigned limited_power:1; unsigned quiescing:1; unsigned disconnected:1; @@ -375,12 +376,13 @@ static void kick_khubd(struct usb_hub *hub) { unsigned long flags; - /* Suppress autosuspend until khubd runs */ - atomic_set(&to_usb_interface(hub->intfdev)->pm_usage_cnt, 1); - spin_lock_irqsave(&hub_event_lock, flags); if (!hub->disconnected && list_empty(&hub->event_list)) { list_add_tail(&hub->event_list, &hub_event_list); + + /* Suppress autosuspend until khubd runs */ + usb_autopm_get_interface_no_resume( + to_usb_interface(hub->intfdev)); wake_up(&khubd_wait); } spin_unlock_irqrestore(&hub_event_lock, flags); @@ -665,7 +667,7 @@ int usb_remove_device(struct usb_device *udev) } enum hub_activation_type { - HUB_INIT, HUB_INIT2, HUB_INIT3, + HUB_INIT, HUB_INIT2, HUB_INIT3, /* INITs must come first */ HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, }; @@ -710,8 +712,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) msecs_to_jiffies(delay)); /* Suppress autosuspend until init is done */ - atomic_set(&to_usb_interface(hub->intfdev)-> - pm_usage_cnt, 1); + usb_autopm_get_interface_no_resume( + to_usb_interface(hub->intfdev)); return; /* Continues at init2: below */ } else { hub_power_on(hub, true); @@ -818,6 +820,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) } init3: hub->quiescing = 0; + hub->init_done = 1; status = usb_submit_urb(hub->urb, GFP_NOIO); if (status < 0) @@ -827,6 +830,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) /* Scan all ports that need attention */ kick_khubd(hub); + + /* Allow autosuspend if it was suppressed */ + if (type <= HUB_INIT3) + usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); } /* Implement the continuations for the delays above */ @@ -854,6 +861,11 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) int i; cancel_delayed_work_sync(&hub->init_work); + if (!hub->init_done) { + hub->init_done = 1; + usb_autopm_put_interface_no_suspend( + to_usb_interface(hub->intfdev)); + } /* khubd and related activity won't re-trigger */ hub->quiescing = 1; @@ -1176,7 +1188,10 @@ static void hub_disconnect(struct usb_interface *intf) /* Take the hub off the event list and don't let it be added again */ spin_lock_irq(&hub_event_lock); - list_del_init(&hub->event_list); + if (!list_empty(&hub->event_list)) { + list_del_init(&hub->event_list); + usb_autopm_put_interface_no_suspend(intf); + } hub->disconnected = 1; spin_unlock_irq(&hub_event_lock); @@ -3235,7 +3250,7 @@ static void hub_events(void) * disconnected while waiting for the lock to succeed. */ usb_lock_device(hdev); if (unlikely(hub->disconnected)) - goto loop; + goto loop2; /* If the hub has died, clean up after it */ if (hdev->state == USB_STATE_NOTATTACHED) { @@ -3384,11 +3399,15 @@ static void hub_events(void) } } -loop_autopm: - /* Allow autosuspend if we're not going to run again */ - if (list_empty(&hub->event_list)) - usb_autopm_enable(intf); -loop: + loop_autopm: + /* Balance the usb_autopm_get_interface() above */ + usb_autopm_put_interface_no_suspend(intf); + loop: + /* Balance the usb_autopm_get_interface_no_resume() in + * kick_khubd() and allow autosuspend. + */ + usb_autopm_put_interface(intf); + loop2: usb_unlock_device(hdev); kref_put(&hub->kref, hub_release); |