Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Alan Stern and committed by
Greg Kroah-Hartman
8520f380 3c4bb71f

+80 -9
+80 -9
drivers/usb/core/hub.c
··· 77 77 unsigned has_indicators:1; 78 78 u8 indicator[USB_MAXCHILDREN]; 79 79 struct delayed_work leds; 80 + struct delayed_work init_work; 80 81 }; 81 82 82 83 ··· 516 515 } 517 516 EXPORT_SYMBOL_GPL(usb_hub_tt_clear_buffer); 518 517 519 - static void hub_power_on(struct usb_hub *hub) 518 + /* If do_delay is false, return the number of milliseconds the caller 519 + * needs to delay. 520 + */ 521 + static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) 520 522 { 521 523 int port1; 522 524 unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2; 525 + unsigned delay; 523 526 u16 wHubCharacteristics = 524 527 le16_to_cpu(hub->descriptor->wHubCharacteristics); 525 528 ··· 542 537 set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); 543 538 544 539 /* Wait at least 100 msec for power to become stable */ 545 - msleep(max(pgood_delay, (unsigned) 100)); 540 + delay = max(pgood_delay, (unsigned) 100); 541 + if (do_delay) 542 + msleep(delay); 543 + return delay; 546 544 } 547 545 548 546 static int hub_hub_status(struct usb_hub *hub, ··· 607 599 } 608 600 609 601 enum hub_activation_type { 610 - HUB_INIT, HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME 602 + HUB_INIT, HUB_INIT2, HUB_INIT3, 603 + HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, 611 604 }; 605 + 606 + static void hub_init_func2(struct work_struct *ws); 607 + static void hub_init_func3(struct work_struct *ws); 612 608 613 609 static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) 614 610 { ··· 620 608 int port1; 621 609 int status; 622 610 bool need_debounce_delay = false; 611 + unsigned delay; 612 + 613 + /* Continue a partial initialization */ 614 + if (type == HUB_INIT2) 615 + goto init2; 616 + if (type == HUB_INIT3) 617 + goto init3; 623 618 624 619 /* After a resume, port power should still be on. 625 620 * For any other type of activation, turn it on. 626 621 */ 627 - if (type != HUB_RESUME) 628 - hub_power_on(hub); 622 + if (type != HUB_RESUME) { 623 + 624 + /* Speed up system boot by using a delayed_work for the 625 + * hub's initial power-up delays. This is pretty awkward 626 + * and the implementation looks like a home-brewed sort of 627 + * setjmp/longjmp, but it saves at least 100 ms for each 628 + * root hub (assuming usbcore is compiled into the kernel 629 + * rather than as a module). It adds up. 630 + * 631 + * This can't be done for HUB_RESUME or HUB_RESET_RESUME 632 + * because for those activation types the ports have to be 633 + * operational when we return. In theory this could be done 634 + * for HUB_POST_RESET, but it's easier not to. 635 + */ 636 + if (type == HUB_INIT) { 637 + delay = hub_power_on(hub, false); 638 + PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2); 639 + schedule_delayed_work(&hub->init_work, 640 + msecs_to_jiffies(delay)); 641 + return; /* Continues at init2: below */ 642 + } else { 643 + hub_power_on(hub, true); 644 + } 645 + } 646 + init2: 629 647 630 648 /* Check each port and set hub->change_bits to let khubd know 631 649 * which ports need attention. ··· 734 692 * If any port-status changes do occur during this delay, khubd 735 693 * will see them later and handle them normally. 736 694 */ 737 - if (need_debounce_delay) 738 - msleep(HUB_DEBOUNCE_STABLE); 695 + if (need_debounce_delay) { 696 + delay = HUB_DEBOUNCE_STABLE; 739 697 698 + /* Don't do a long sleep inside a workqueue routine */ 699 + if (type == HUB_INIT2) { 700 + PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3); 701 + schedule_delayed_work(&hub->init_work, 702 + msecs_to_jiffies(delay)); 703 + return; /* Continues at init3: below */ 704 + } else { 705 + msleep(delay); 706 + } 707 + } 708 + init3: 740 709 hub->quiescing = 0; 741 710 742 711 status = usb_submit_urb(hub->urb, GFP_NOIO); ··· 760 707 kick_khubd(hub); 761 708 } 762 709 710 + /* Implement the continuations for the delays above */ 711 + static void hub_init_func2(struct work_struct *ws) 712 + { 713 + struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); 714 + 715 + hub_activate(hub, HUB_INIT2); 716 + } 717 + 718 + static void hub_init_func3(struct work_struct *ws) 719 + { 720 + struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); 721 + 722 + hub_activate(hub, HUB_INIT3); 723 + } 724 + 763 725 enum hub_quiescing_type { 764 726 HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND 765 727 }; ··· 783 715 { 784 716 struct usb_device *hdev = hub->hdev; 785 717 int i; 718 + 719 + cancel_delayed_work_sync(&hub->init_work); 786 720 787 721 /* khubd and related activity won't re-trigger */ 788 722 hub->quiescing = 1; ··· 1169 1099 hub->intfdev = &intf->dev; 1170 1100 hub->hdev = hdev; 1171 1101 INIT_DELAYED_WORK(&hub->leds, led_work); 1102 + INIT_DELAYED_WORK(&hub->init_work, NULL); 1172 1103 usb_get_intf(intf); 1173 1104 1174 1105 usb_set_intfdata (intf, hub); ··· 3106 3035 i); 3107 3036 clear_port_feature(hdev, i, 3108 3037 USB_PORT_FEAT_C_OVER_CURRENT); 3109 - hub_power_on(hub); 3038 + hub_power_on(hub, true); 3110 3039 } 3111 3040 3112 3041 if (portchange & USB_PORT_STAT_C_RESET) { ··· 3141 3070 dev_dbg (hub_dev, "overcurrent change\n"); 3142 3071 msleep(500); /* Cool down */ 3143 3072 clear_hub_feature(hdev, C_HUB_OVER_CURRENT); 3144 - hub_power_on(hub); 3073 + hub_power_on(hub, true); 3145 3074 } 3146 3075 } 3147 3076