summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2008-09-22 14:44:26 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2008-10-17 14:40:57 -0700
commit8520f38099ccfdac2147a0852f84ee7a8ee5e197 (patch)
treecefc0256d9af7088992b7ed02bdae8ba95bf68fa
parent3c4bb71f96c69ef3c81fda108c96b633a2000de2 (diff)
USB: change hub initialization sleeps to delayed_work
This patch (as1137) changes the hub_activate() routine, replacing the power-power-up and debounce delays with delayed_work calls. The idea is that on systems where the USB stack is compiled into the kernel rather than built as modules, these delays will no longer block the boot thread. At least 100 ms is saved for each root hub, which can add up to a significant savings in total boot time. Arjan van de Ven was very pleased to see that this shaved 700 ms off his computer's boot time. Since his total boot time is on the order of two seconds, the improvement is considerable. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested-by: Arjan van de Ven <arjan@infradead.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/core/hub.c91
1 files changed, 81 insertions, 10 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d99963873e3..b97110ca352 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -77,6 +77,7 @@ struct usb_hub {
unsigned has_indicators:1;
u8 indicator[USB_MAXCHILDREN];
struct delayed_work leds;
+ struct delayed_work init_work;
};
@@ -515,10 +516,14 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
}
EXPORT_SYMBOL_GPL(usb_hub_tt_clear_buffer);
-static void hub_power_on(struct usb_hub *hub)
+/* If do_delay is false, return the number of milliseconds the caller
+ * needs to delay.
+ */
+static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
{
int port1;
unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2;
+ unsigned delay;
u16 wHubCharacteristics =
le16_to_cpu(hub->descriptor->wHubCharacteristics);
@@ -537,7 +542,10 @@ static void hub_power_on(struct usb_hub *hub)
set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
/* Wait at least 100 msec for power to become stable */
- msleep(max(pgood_delay, (unsigned) 100));
+ delay = max(pgood_delay, (unsigned) 100);
+ if (do_delay)
+ msleep(delay);
+ return delay;
}
static int hub_hub_status(struct usb_hub *hub,
@@ -599,21 +607,55 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
}
enum hub_activation_type {
- HUB_INIT, HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME
+ HUB_INIT, HUB_INIT2, HUB_INIT3,
+ HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
};
+static void hub_init_func2(struct work_struct *ws);
+static void hub_init_func3(struct work_struct *ws);
+
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
struct usb_device *hdev = hub->hdev;
int port1;
int status;
bool need_debounce_delay = false;
+ unsigned delay;
+
+ /* Continue a partial initialization */
+ if (type == HUB_INIT2)
+ goto init2;
+ if (type == HUB_INIT3)
+ goto init3;
/* After a resume, port power should still be on.
* For any other type of activation, turn it on.
*/
- if (type != HUB_RESUME)
- hub_power_on(hub);
+ if (type != HUB_RESUME) {
+
+ /* Speed up system boot by using a delayed_work for the
+ * hub's initial power-up delays. This is pretty awkward
+ * and the implementation looks like a home-brewed sort of
+ * setjmp/longjmp, but it saves at least 100 ms for each
+ * root hub (assuming usbcore is compiled into the kernel
+ * rather than as a module). It adds up.
+ *
+ * This can't be done for HUB_RESUME or HUB_RESET_RESUME
+ * because for those activation types the ports have to be
+ * operational when we return. In theory this could be done
+ * for HUB_POST_RESET, but it's easier not to.
+ */
+ if (type == HUB_INIT) {
+ delay = hub_power_on(hub, false);
+ PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2);
+ schedule_delayed_work(&hub->init_work,
+ msecs_to_jiffies(delay));
+ return; /* Continues at init2: below */
+ } else {
+ hub_power_on(hub, true);
+ }
+ }
+ init2:
/* Check each port and set hub->change_bits to let khubd know
* which ports need attention.
@@ -692,9 +734,20 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
* If any port-status changes do occur during this delay, khubd
* will see them later and handle them normally.
*/
- if (need_debounce_delay)
- msleep(HUB_DEBOUNCE_STABLE);
-
+ if (need_debounce_delay) {
+ delay = HUB_DEBOUNCE_STABLE;
+
+ /* Don't do a long sleep inside a workqueue routine */
+ if (type == HUB_INIT2) {
+ PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3);
+ schedule_delayed_work(&hub->init_work,
+ msecs_to_jiffies(delay));
+ return; /* Continues at init3: below */
+ } else {
+ msleep(delay);
+ }
+ }
+ init3:
hub->quiescing = 0;
status = usb_submit_urb(hub->urb, GFP_NOIO);
@@ -707,6 +760,21 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
kick_khubd(hub);
}
+/* Implement the continuations for the delays above */
+static void hub_init_func2(struct work_struct *ws)
+{
+ struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work);
+
+ hub_activate(hub, HUB_INIT2);
+}
+
+static void hub_init_func3(struct work_struct *ws)
+{
+ struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work);
+
+ hub_activate(hub, HUB_INIT3);
+}
+
enum hub_quiescing_type {
HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND
};
@@ -716,6 +784,8 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
struct usb_device *hdev = hub->hdev;
int i;
+ cancel_delayed_work_sync(&hub->init_work);
+
/* khubd and related activity won't re-trigger */
hub->quiescing = 1;
@@ -1099,6 +1169,7 @@ descriptor_error:
hub->intfdev = &intf->dev;
hub->hdev = hdev;
INIT_DELAYED_WORK(&hub->leds, led_work);
+ INIT_DELAYED_WORK(&hub->init_work, NULL);
usb_get_intf(intf);
usb_set_intfdata (intf, hub);
@@ -3035,7 +3106,7 @@ static void hub_events(void)
i);
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_OVER_CURRENT);
- hub_power_on(hub);
+ hub_power_on(hub, true);
}
if (portchange & USB_PORT_STAT_C_RESET) {
@@ -3070,7 +3141,7 @@ static void hub_events(void)
dev_dbg (hub_dev, "overcurrent change\n");
msleep(500); /* Cool down */
clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
- hub_power_on(hub);
+ hub_power_on(hub, true);
}
}