This allows the boot to progress while USB is being probed - which otherwise takes about 70ms per controller on my Tegra2 system. It was mentioned some years ago in an email from Linus Torvalds: https://lkml.org/lkml/2008/10/10/411 > - they call usb_add_hcd, and usb_add_hcd is a horrible and slow > piece of crap that doesn't just add the host controller, but does all > the probing too. > > In other words, what should be fixed is not the initcall sequence, > and certainly not make PCI device probing (or other random buses) be > partly asynchronous, but simply make that USB host controller startup > function be asynchronous. It might be better to delay the work until much later unless USB is needed for the root disk, but that might have to be a command line option. Signed-off-by: Simon Glass <sjg@xxxxxxxxxxxx> --- drivers/usb/core/hcd.c | 75 +++++++++++++++++++++++++++++++++++++++------- drivers/usb/core/usb.c | 5 +++ include/linux/usb/hcd.h | 9 +++++ 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index eb19cba..a201062 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -111,6 +111,9 @@ static DEFINE_SPINLOCK(hcd_urb_unlink_lock); /* wait queue for synchronous unlinks */ DECLARE_WAIT_QUEUE_HEAD(usb_kill_urb_queue); +/* work queue to handle reset and probing */ +static struct workqueue_struct *hcd_workq; + static inline int is_root_hub(struct usb_device *udev) { return (udev->parent == NULL); @@ -2362,17 +2365,7 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd, return 0; } -/** - * usb_add_hcd - finish generic HCD structure initialization and register - * @hcd: the usb_hcd structure to initialize - * @irqnum: Interrupt line to allocate - * @irqflags: Interrupt type flags - * - * Finish the remaining parts of generic HCD initialization: allocate the - * buffers of consistent memory, register the bus, request the IRQ line, - * and call the driver's reset() and start() routines. - */ -int usb_add_hcd(struct usb_hcd *hcd, +int usb_add_hcd_work(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags) { int retval; @@ -2517,7 +2510,50 @@ err_allocate_root_hub: err_register_bus: hcd_buffer_destroy(hcd); return retval; -} +} + +/* This is the work function for usb_add_hcd() */ +void probe_hcd(struct work_struct *item) +{ + struct usb_hcd *hcd = container_of(item, struct usb_hcd, + init_work.work); + int err; + + err = usb_add_hcd_work(hcd, hcd->init_irqnum, hcd->init_irqflags); + if (err) + printk(KERN_ERR "probe_hcd failed with error %d\n", err); +} + +/** + * usb_add_hcd - finish generic HCD structure initialization and register + * @hcd: the usb_hcd structure to initialize + * @irqnum: Interrupt line to allocate + * @irqflags: Interrupt type flags + * + * Finish the remaining parts of generic HCD initialization: allocate the + * buffers of consistent memory, register the bus, request the IRQ line, + * and call the driver's reset() and start() routines. + */ +int usb_add_hcd(struct usb_hcd *hcd, + unsigned int irqnum, unsigned long irqflags) +{ + /* + * Perhaps we should have a pointer to an allocated structure since + * these fields are not used after init. + */ + INIT_DELAYED_WORK(&hcd->init_work, probe_hcd); + hcd->init_irqnum = irqnum; + hcd->init_irqflags = irqflags; + + /* + * I'm sure we can't delay this by a second. Should we start it + * immediately? Are we allowed to delay a little? Sometimes USB will + * provide the root disk, so perhaps not. + */ + if (!queue_delayed_work(hcd_workq, &hcd->init_work, HZ)) + return -ENOMEM; + return 0; +} EXPORT_SYMBOL_GPL(usb_add_hcd); /** @@ -2591,6 +2627,21 @@ usb_hcd_platform_shutdown(struct platform_device* dev) } EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); +int usb_hcd_init(void) +{ + hcd_workq = alloc_workqueue("usb_hcd", + WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 1); + if (!hcd_workq) + return -ENOMEM; + + return 0; +} + +void usb_hcd_cleanup(void) +{ + destroy_workqueue(hcd_workq); +} + /*-------------------------------------------------------------------------*/ #if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 8ca9f99..5e5b944 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1036,10 +1036,14 @@ static int __init usb_init(void) retval = usb_hub_init(); if (retval) goto hub_init_failed; + retval = usb_hcd_init(); + if (retval) + goto hcd_init_failed; retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); if (!retval) goto out; +hcd_init_failed: usb_hub_cleanup(); hub_init_failed: usbfs_cleanup(); @@ -1069,6 +1073,7 @@ static void __exit usb_exit(void) return; usb_deregister_device_driver(&usb_generic_driver); + usb_hcd_cleanup(); usb_major_cleanup(); usbfs_cleanup(); usb_deregister(&usbfs_driver); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index b2f62f3..49e445b 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -87,6 +87,9 @@ struct usb_hcd { #ifdef CONFIG_USB_SUSPEND struct work_struct wakeup_work; /* for remote wakeup */ #endif + struct delayed_work init_work; /* for initial init */ + unsigned int init_irqnum; /* requested irqnum */ + unsigned long init_irqflags; /* requested irq flags */ /* * hardware info/state @@ -668,6 +671,12 @@ extern struct rw_semaphore ehci_cf_port_reset_rwsem; #define USB_EHCI_LOADED 2 extern unsigned long usb_hcds_loaded; +/* Initalise the HCD ready for use, Must be called before usb_add_hcd() */ +int usb_hcd_init(void); + +/* Clean up HCD */ +void usb_hcd_cleanup(void); + #endif /* __KERNEL__ */ #endif /* __USB_CORE_HCD_H */ -- 1.7.7.3 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html