[PATCH] usb: Use a workqueue in usb_add_hcd() to reduce boot time

From: Simon Glass
Date: Thu Jan 19 2012 - 18:38:20 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/