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. One usb_hcd structure is designated to be the "primary HCD", and a pointer is added to the usb_hcd structure to keep track of that. A new function call, usb_hcd_is_primary_hcd() is added to check whether the USB hcd is marked as the primary HCD (or if it is not part of a roothub pair). To allow the USB core and xHCI driver to access either roothub in a pair, a "shared_hcd" pointer is added to the usb_hcd structure. 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 primary_hcd pointer argument is NULL. If it is passed a non-NULL primary_hcd pointer, it sets usb_hcd->shared_hcd and usb_hcd->primary_hcd fields. It will also skip the bandwidth_mutex allocation, and set the secondary hcd's bandwidth_mutex pointer to the primary HCD's mutex. IRQs are only allocated once for the primary roothub. Introduce a new usb_hcd driver flag that indicates the host controller driver wants to create two roothubs. If the HCD_SHARED flag is set, then the USB core PCI probe methods will allocate a second roothub, and make sure that second roothub gets freed during rmmod and in initialization error paths. If the driver that wants to create two roothubs is an xHCI driver, the roothub that is allocated last is marked as the USB 2.0 roothub. Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> --- drivers/usb/core/hcd-pci.c | 38 +++++++++++++++++ drivers/usb/core/hcd.c | 95 +++++++++++++++++++++++++++++++++++-------- include/linux/usb/hcd.h | 7 +++ 3 files changed, 122 insertions(+), 18 deletions(-) diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 5792bb4..cf3fcbe 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -172,6 +172,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct hc_driver *driver; struct usb_hcd *hcd; + struct usb_hcd *shared_hcd = NULL; int retval; if (usb_disabled()) @@ -200,6 +201,17 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) retval = -ENOMEM; goto disable_pci; } + if (driver->flags & HCD_SHARED) { + shared_hcd = usb_create_shared_hcd(driver, &dev->dev, + pci_name(dev), hcd); + if (!shared_hcd) { + retval = -ENOMEM; + goto dealloc_hcd; + } + /* Mark the non-primary HCD as the xHCI USB 2.0 roothub */ + if ((driver->flags & HCD_MASK) == HCD_USB3) + shared_hcd->bcdUSB = HCD_USB2; + } if (driver->flags & HCD_MEMORY) { /* EHCI, OHCI */ @@ -245,12 +257,20 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); if (retval != 0) goto unmap_registers; + if (driver->flags & HCD_SHARED) { + retval = usb_add_hcd(shared_hcd, dev->irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval != 0) + goto remove_hcd; + } set_hs_companion(dev, hcd); if (pci_dev_run_wake(dev)) pm_runtime_put_noidle(&dev->dev); return retval; +remove_hcd: + usb_remove_hcd(hcd); unmap_registers: if (driver->flags & HCD_MEMORY) { iounmap(hcd->regs); @@ -260,6 +280,8 @@ release_mem_region: release_region(hcd->rsrc_start, hcd->rsrc_len); clear_companion: clear_hs_companion(dev, hcd); + usb_put_hcd(shared_hcd); +dealloc_hcd: usb_put_hcd(hcd); disable_pci: pci_disable_device(dev); @@ -286,6 +308,7 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_probe); void usb_hcd_pci_remove(struct pci_dev *dev) { struct usb_hcd *hcd; + struct usb_hcd *shared_hcd; hcd = pci_get_drvdata(dev); if (!hcd) @@ -302,15 +325,30 @@ void usb_hcd_pci_remove(struct pci_dev *dev) usb_hcd_irq(0, hcd); local_irq_enable(); + if (hcd->driver->flags & HCD_SHARED) + shared_hcd = hcd->shared_hcd; + else + shared_hcd = NULL; + + if (shared_hcd) + usb_remove_hcd(shared_hcd); usb_remove_hcd(hcd); + if (hcd->driver->flags & HCD_MEMORY) { iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); } else { release_region(hcd->rsrc_start, hcd->rsrc_len); } + + if (shared_hcd) + clear_hs_companion(dev, shared_hcd); clear_hs_companion(dev, hcd); + + if (shared_hcd) + usb_put_hcd(shared_hcd); usb_put_hcd(hcd); + pci_disable_device(dev); } EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 79bad2c..a061c31 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2132,10 +2132,12 @@ 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 + * @primary_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Only allocate certain resources for the primary HCD * Context: !in_interrupt() * * Allocate a struct usb_hcd, with extra space at the end for the @@ -2144,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 *primary_hcd) { struct usb_hcd *hcd; @@ -2154,16 +2157,24 @@ 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 (primary_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 = primary_hcd->bandwidth_mutex; + hcd->primary_hcd = primary_hcd; + primary_hcd->primary_hcd = primary_hcd; + hcd->shared_hcd = primary_hcd; + primary_hcd->shared_hcd = hcd; } - mutex_init(hcd->bandwidth_mutex); - dev_set_drvdata(dev, hcd); kref_init(&hcd->kref); usb_bus_init(&hcd->self); @@ -2184,13 +2195,49 @@ 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); +/* + * Roothubs that share one PCI device must also share the bandwidth mutex. + * Don't deallocate the bandwidth_mutex until the last shared usb_hcd is + * deallocated. + * + * 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. + */ static void hcd_release (struct kref *kref) { struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); - kfree(hcd->bandwidth_mutex); + if (hcd->shared_hcd) + hcd->shared_hcd->shared_hcd = NULL; + else + kfree(hcd->bandwidth_mutex); kfree(hcd); } @@ -2209,6 +2256,14 @@ void usb_put_hcd (struct usb_hcd *hcd) } EXPORT_SYMBOL_GPL(usb_put_hcd); +inline int usb_hcd_is_primary_hcd(struct usb_hcd *hcd) +{ + if (!hcd->primary_hcd) + return 1; + return hcd == hcd->primary_hcd; +} +EXPORT_SYMBOL_GPL(usb_hcd_is_primary_hcd); + static int usb_hcd_request_irqs(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags) { @@ -2324,9 +2379,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_primary_hcd(hcd)) { + retval = usb_hcd_request_irqs(hcd, irqnum, irqflags); + if (retval) + goto err_request_irq; + } retval = hcd->driver->start(hcd); if (retval < 0) { @@ -2371,7 +2428,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_primary_hcd(hcd) && hcd->irq >= 0) free_irq(irqnum, hcd); err_request_irq: err_hcd_driver_setup: @@ -2434,8 +2491,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_primary_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 ac2dc66..d8b17d4 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -142,6 +142,8 @@ struct usb_hcd { * to the device, or resetting the bandwidth after a failed attempt. */ struct mutex *bandwidth_mutex; + struct usb_hcd *shared_hcd; + struct usb_hcd *primary_hcd; #define HCD_BUFFER_POOLS 4 @@ -204,6 +206,7 @@ struct hc_driver { int flags; #define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ #define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ +#define HCD_SHARED 0x0004 /* Two (or more) usb_hcds share HW */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ #define HCD_USB3 0x0040 /* USB 3.0 */ @@ -350,8 +353,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_primary_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