[RFC 10/22] usb: Make core allocate resources per PCI-device.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux