Introduce the notion of a PCI device that may be associated with more than one USB host controller driver (struct usb_hcd). This patch is the start of the work to separate the xHCI host controller into two roothubs: a USB 3.0 roothub with SuperSpeed-only ports, and a USB 2.0 roothub with HS/FS/LS ports. Only one pointer can be stored in the PCI device's private pointer, so designate one roothub as the "main" hcd. For xHCI, this will always be the USB 3.0 roothub. Introduce usb_hcd_is_main_hcd(), a way to check whether a usb_hcd structure pointer matches the one stored in the hcd->self.controller device's private pointer. Make sure that only one interrupt is requested for the whole PCI device, by using usb_hcd_is_main_hcd(). Add a new function, usb_create_shared_hcd(), that does roothub allocation for paired roothubs. It will act just like usb_create_hcd() did if the shared_hcd pointer argument is NULL. If it is passed a non-NULL shared_hcd pointer, it sets usb_hcd->shared_hcd, so each roothub can access the other partner in the pair. The bandwidth_mutex needs to be shared across both xHCI roothubs, so make usb_create_shared_hcd() only allocate the mutex once. When it is passed a valid shared_hcd pointer, it stores the mutex pointer from the partner in the new roothub. The bandwidth mutex is only deallocated when the shared_hcd pointer is NULL. The first shared roothub that is released will set shared_hcd to NULL. The second shared roothub that is released (or a USB host controller driver that doesn't set up shared roothubs) will deallocate the bandwidth mutex. Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> --- drivers/usb/core/hcd.c | 86 +++++++++++++++++++++++++++++++++++++---------- include/linux/usb/hcd.h | 5 +++ 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 6883fe9..091da8e 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2130,10 +2130,14 @@ EXPORT_SYMBOL_GPL (usb_hc_died); /*-------------------------------------------------------------------------*/ /** - * usb_create_hcd - create and initialize an HCD structure + * usb_create_shared_hcd - create and initialize an HCD structure * @driver: HC driver that will use this hcd * @dev: device for this HC, stored in hcd->self.controller * @bus_name: value to store in hcd->self.bus_name + * @shared_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Used for allocating two usb_hcd structures + * under one xHCI host controller PCI device (a USB 2.0 bus + * and a USB 3.0 bus). * Context: !in_interrupt() * * Allocate a struct usb_hcd, with extra space at the end for the @@ -2142,8 +2146,9 @@ EXPORT_SYMBOL_GPL (usb_hc_died); * * If memory is unavailable, returns NULL. */ -struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, - struct device *dev, const char *bus_name) +struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name, + struct usb_hcd *shared_hcd) { struct usb_hcd *hcd; @@ -2152,16 +2157,23 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, dev_dbg (dev, "hcd alloc failed\n"); return NULL; } - hcd->bandwidth_mutex = kmalloc(sizeof(*hcd->bandwidth_mutex), - GFP_KERNEL); - if (!hcd->bandwidth_mutex) { - kfree(hcd); - dev_dbg(dev, "hcd bandwidth mutex alloc failed\n"); - return NULL; + if (shared_hcd == NULL) { + hcd->bandwidth_mutex = kmalloc(sizeof(*hcd->bandwidth_mutex), + GFP_KERNEL); + if (!hcd->bandwidth_mutex) { + kfree(hcd); + dev_dbg(dev, "hcd bandwidth mutex alloc failed\n"); + return NULL; + } + mutex_init(hcd->bandwidth_mutex); + dev_set_drvdata(dev, hcd); + } else { + hcd->bandwidth_mutex = shared_hcd->bandwidth_mutex; + /* Don't overwrite the device data if we're sharing usb_hcds */ + hcd->shared_hcd = shared_hcd; + shared_hcd->shared_hcd = hcd; } - mutex_init(hcd->bandwidth_mutex); - dev_set_drvdata(dev, hcd); kref_init(&hcd->kref); usb_bus_init(&hcd->self); @@ -2181,13 +2193,41 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, "USB Host Controller"; return hcd; } +EXPORT_SYMBOL_GPL(usb_create_shared_hcd); + +/** + * usb_create_hcd - create and initialize an HCD structure + * @driver: HC driver that will use this hcd + * @dev: device for this HC, stored in hcd->self.controller + * @bus_name: value to store in hcd->self.bus_name + * @shared_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Used for allocating two usb_hcd structures + * under one xHCI host controller PCI device (a USB 2.0 bus + * and a USB 3.0 bus). + * Context: !in_interrupt() + * + * Allocate a struct usb_hcd, with extra space at the end for the + * HC driver's private data. Initialize the generic members of the + * hcd structure. + * + * If memory is unavailable, returns NULL. + */ +struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name) +{ + return usb_create_shared_hcd(driver, dev, bus_name, NULL); +} EXPORT_SYMBOL_GPL(usb_create_hcd); static void hcd_release (struct kref *kref) { struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); - kfree(hcd->bandwidth_mutex); + /* Last freed roothub frees the shared bandwidth mutex */ + if (hcd->shared_hcd) + hcd->shared_hcd->shared_hcd = NULL; + else + kfree(hcd->bandwidth_mutex); kfree(hcd); } @@ -2206,6 +2246,12 @@ void usb_put_hcd (struct usb_hcd *hcd) } EXPORT_SYMBOL_GPL(usb_put_hcd); +inline int usb_hcd_is_main_hcd(struct usb_hcd *hcd) +{ + return hcd == dev_get_drvdata(hcd->self.controller); +} +EXPORT_SYMBOL_GPL(usb_hcd_is_main_hcd); + static int usb_hcd_request_irqs(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags) { @@ -2319,9 +2365,11 @@ int usb_add_hcd(struct usb_hcd *hcd, dev_dbg(hcd->self.controller, "supports USB remote wakeup\n"); /* enable irqs just before we start the controller */ - retval = usb_hcd_request_irqs(hcd, irqnum, irqflags); - if (retval) - goto err_request_irq; + if (usb_hcd_is_main_hcd(hcd)) { + retval = usb_hcd_request_irqs(hcd, irqnum, irqflags); + if (retval) + goto err_request_irq; + } if ((retval = hcd->driver->start(hcd)) < 0) { dev_err(hcd->self.controller, "startup error %d\n", retval); @@ -2365,7 +2413,7 @@ err_register_root_hub: clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); del_timer_sync(&hcd->rh_timer); err_hcd_driver_start: - if (hcd->irq >= 0) + if (usb_hcd_is_main_hcd(hcd) && hcd->irq >= 0) free_irq(irqnum, hcd); err_request_irq: err_hcd_driver_setup: @@ -2428,8 +2476,10 @@ void usb_remove_hcd(struct usb_hcd *hcd) clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); del_timer_sync(&hcd->rh_timer); - if (hcd->irq >= 0) - free_irq(hcd->irq, hcd); + if (usb_hcd_is_main_hcd(hcd)) { + if (hcd->irq >= 0) + free_irq(hcd->irq, hcd); + } usb_put_dev(hcd->self.root_hub); usb_deregister_bus(&hcd->self); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 5a29e4e..e7de392 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -138,6 +138,7 @@ struct usb_hcd { * to the device, or resetting the bandwidth after a failed attempt. */ struct mutex *bandwidth_mutex; + struct usb_hcd *shared_hcd; #define HCD_BUFFER_POOLS 4 @@ -346,8 +347,12 @@ extern int usb_hcd_get_frame_number(struct usb_device *udev); extern struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name); +extern struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name, + struct usb_hcd *shared_hcd); extern struct usb_hcd *usb_get_hcd(struct usb_hcd *hcd); extern void usb_put_hcd(struct usb_hcd *hcd); +extern inline int usb_hcd_is_main_hcd(struct usb_hcd *hcd); extern int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags); extern void usb_remove_hcd(struct usb_hcd *hcd); -- 1.6.3.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