[RFC PATCH 02/15] usbcore: introduce usb_domain for tracking port-connector relationships

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

 



Platform firmware identifies ports that share a connector.  This
information is relevant for port power control since we do not want a
device to reconnect on its peer port in a connector when pm has decided
to power-off its currently connected port.

In the case of xhci, peer ports are attached through separate root hubs
(and zero or more integrated hubs).  'struct usb_domain' is introduced
so that each root hub 'hcd' can register its ports to a unified
'platform' domain where connector relationships can be evaluated.  A
connector is formed when two ports are registered with identical 'match
data'.  ACPI defines connectors by specifying a common 'group token' and
'group position' property for the port [1].  Outside of this match data
there is nothing firmware specific about this implementation, hence the
creation of the common rountines in usb-platform.c.

[1] ACPI 5.0 Section 6.1.8 _PLD (Physical Device Location)

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/Kconfig        |    4 +
 drivers/usb/core/Makefile       |    1 
 drivers/usb/core/hcd-pci.c      |   17 +++-
 drivers/usb/core/hcd.c          |    2 
 drivers/usb/core/hub.h          |    8 ++
 drivers/usb/core/usb-acpi.c     |   69 +++++++++++++++--
 drivers/usb/core/usb-platform.c |  162 +++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/usb-platform.h |   44 +++++++++++
 drivers/usb/host/xhci-pci.c     |   16 +++-
 include/linux/usb/hcd.h         |    9 ++
 10 files changed, 320 insertions(+), 12 deletions(-)
 create mode 100644 drivers/usb/core/usb-platform.c
 create mode 100644 drivers/usb/core/usb-platform.h

diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index db535b0aa172..e439a14b0210 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -89,3 +89,7 @@ config USB_OTG_BLACKLIST_HUB
 	  and software costs by not supporting external hubs.  So
 	  are "Embedded Hosts" that don't offer OTG support.
 
+config USB_PLATFORM
+	bool
+	default y if ACPI
+
diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile
index 5e847ad2f58a..b534b9b28c69 100644
--- a/drivers/usb/core/Makefile
+++ b/drivers/usb/core/Makefile
@@ -11,5 +11,6 @@ usbcore-y += port.o
 
 usbcore-$(CONFIG_PCI)		+= hcd-pci.o
 usbcore-$(CONFIG_ACPI)		+= usb-acpi.o
+usbcore-$(CONFIG_USB_PLATFORM)  += usb-platform.o
 
 obj-$(CONFIG_USB)		+= usbcore.o
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index dfe9d0f22978..542dc0f6ef74 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -161,20 +161,23 @@ static void ehci_wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd,
 /* always called with process context; sleeping is OK */
 
 /**
- * usb_hcd_pci_probe - initialize PCI-based HCDs
+ * usb_hcd_pci_probe_domain - initialize PCI-based HCDs
  * @dev: USB Host Controller being probed
  * @id: pci hotplug id connecting controller to HCD framework
+ * @domain: if this hcd coordinates port activity with another hcd from pdev
  * Context: !in_interrupt()
  *
  * Allocates basic PCI resources for this USB host controller, and
  * then invokes the start() method for the HCD associated with it
  * through the hotplug entry's driver_data.
  *
- * Store this function in the HCD's struct pci_driver as probe().
+ * Note the usb_domain is not to be confused with companion controllers which
+ * multiplex the same phy with multiple controllers.
  *
  * Return: 0 if successful.
  */
-int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+int usb_hcd_pci_probe_domain(struct pci_dev *dev, struct usb_domain *udom,
+			     const struct pci_device_id *id)
 {
 	struct hc_driver	*driver;
 	struct usb_hcd		*hcd;
@@ -215,6 +218,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		goto disable_pci;
 	}
 
+	hcd->domain = usb_get_domain(udom);
 	hcd->amd_resume_bug = (usb_hcd_amd_remote_wakeup_quirk(dev) &&
 			driver->flags & (HCD_USB11 | HCD_USB3)) ? 1 : 0;
 
@@ -301,6 +305,13 @@ disable_pci:
 	dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);
 	return retval;
 }
+EXPORT_SYMBOL_GPL(usb_hcd_pci_probe_domain);
+
+
+int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	return usb_hcd_pci_probe_domain(dev, NULL, id);
+}
 EXPORT_SYMBOL_GPL(usb_hcd_pci_probe);
 
 
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 6bffb8c87bc9..892574f439a4 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2505,6 +2505,8 @@ static void hcd_release (struct kref *kref)
 		kfree(hcd->bandwidth_mutex);
 	else
 		hcd->shared_hcd->shared_hcd = NULL;
+
+	usb_put_domain(hcd->domain);
 	kfree(hcd);
 }
 
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 4e4790dea343..9ea075d1b7a3 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -19,6 +19,9 @@
  * for more details.
  */
 
+#ifndef __USB_HUB_H__
+#define __USB_HUB_H__
+
 #include <linux/usb.h>
 #include <linux/usb/ch11.h>
 #include <linux/usb/hcd.h>
@@ -80,6 +83,8 @@ struct usb_hub {
  * struct usb port - kernel's representation of a usb port
  * @child: usb device attatched to the port
  * @dev: generic device interface
+ * @node: peer ports in a given connector
+ * @connector: parent connector for this port
  * @port_owner: port's owner
  * @connect_type: port's connect type
  * @portnum: port index num based one
@@ -88,6 +93,8 @@ struct usb_hub {
  */
 struct usb_port {
 	struct usb_device *child;
+	struct list_head node;
+	struct usb_connector *connector;
 	struct device dev;
 	struct dev_state *port_owner;
 	enum usb_port_connect_type connect_type;
@@ -123,3 +130,4 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub,
 	return hub_port_debounce(hub, port1, false);
 }
 
+#endif /* __USB_HUB_H__ */
diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c
index 255c14464bf2..fab4a8288803 100644
--- a/drivers/usb/core/usb-acpi.c
+++ b/drivers/usb/core/usb-acpi.c
@@ -18,7 +18,8 @@
 #include <linux/usb/hcd.h>
 #include <acpi/acpi_bus.h>
 
-#include "usb.h"
+#include "hub.h"
+#include "usb-platform.h"
 
 /**
  * usb_acpi_power_manageable - check whether usb port has
@@ -42,6 +43,51 @@ bool usb_acpi_power_manageable(struct usb_device *hdev, int index)
 }
 EXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
 
+struct usb_acpi_pair_data {
+	u8 group_token;
+	u8 group_position;
+};
+
+static void usb_acpi_pair_port(struct usb_device *hdev, acpi_handle *handle,
+			       int port1, struct acpi_pld_info *pld)
+{
+	struct usb_acpi_pair_data data;
+	struct usb_port *port_dev;
+	struct usb_hcd *hcd;
+	struct usb_hub *hub;
+
+	if (!pld)
+		return;
+
+	hub = usb_hub_to_struct_hub(hdev);
+	if (!hub)
+		return;
+
+	port_dev = hub->ports[port1 - 1];
+	hcd = bus_to_hcd(hdev->bus);
+
+	memset(&data, 0, sizeof(data));
+	data.group_token = pld->group_token;
+	data.group_position = pld->group_position;
+	usb_domain_pair_port(hcd->domain, port_dev, &data, sizeof(data));
+}
+
+static void usb_acpi_cleanup(struct device *dev)
+{
+	struct usb_port *port_dev;
+	struct usb_device *hdev;
+	struct usb_hcd *hcd;
+
+	if (!is_usb_port(dev))
+		return;
+
+	hdev = to_usb_device(dev->parent->parent);
+	port_dev = to_usb_port(dev);
+	hcd = bus_to_hcd(hdev->bus);
+
+	usb_domain_remove_port(hcd->domain, port_dev);
+}
+
 /**
  * usb_acpi_set_power_state - control usb port's power via acpi power
  * resource
@@ -83,12 +129,11 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
 EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
 
 static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
-	acpi_handle handle, int port1)
+	acpi_handle handle, int port1, struct acpi_pld_info *pld)
 {
 	acpi_status status;
 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 	union acpi_object *upc;
-	struct acpi_pld_info *pld;
 	int ret = 0;
 
 	/*
@@ -99,8 +144,7 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
 	 * a usb device is directly hard-wired to the port. If no visible and
 	 * no connectable, the port would be not used.
 	 */
-	status = acpi_get_physical_device_location(handle, &pld);
-	if (ACPI_FAILURE(status))
+	if (!pld)
 		return -ENODEV;
 
 	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
@@ -122,7 +166,6 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
 		usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);
 
 out:
-	ACPI_FREE(pld);
 	kfree(upc);
 	return ret;
 }
@@ -179,6 +222,9 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
 			return -ENODEV;
 		return 0;
 	} else if (is_usb_port(dev)) {
+		struct acpi_pld_info *pld;
+		acpi_status status;
+
 		sscanf(dev_name(dev), "port%d", &port_num);
 		/* Get the struct usb_device point of port's hub */
 		udev = to_usb_device(dev->parent->parent);
@@ -209,7 +255,15 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
 			if (!*handle)
 				return -ENODEV;
 		}
-		usb_acpi_check_port_connect_type(udev, *handle, port_num);
+
+		status = acpi_get_physical_device_location(*handle, &pld);
+		if (ACPI_FAILURE(status))
+			pld = NULL;
+
+		usb_acpi_check_port_connect_type(udev, *handle, port_num, pld);
+		usb_acpi_pair_port(udev, *handle, port_num, pld);
+
+		ACPI_FREE(pld);
 	} else
 		return -ENODEV;
 
@@ -225,6 +279,7 @@ static struct acpi_bus_type usb_acpi_bus = {
 	.name = "USB",
 	.match = usb_acpi_bus_match,
 	.find_device = usb_acpi_find_device,
+	.cleanup = usb_acpi_cleanup,
 };
 
 int usb_acpi_register(void)
diff --git a/drivers/usb/core/usb-platform.c b/drivers/usb/core/usb-platform.c
new file mode 100644
index 000000000000..476ac3fa47c2
--- /dev/null
+++ b/drivers/usb/core/usb-platform.c
@@ -0,0 +1,162 @@
+/*
+ * USB support for platform defined connectors / port-pairings
+ *
+ * Copyright 2013 Intel Corporation <dan.j.williams@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, version 2.
+ *
+ */
+#include <linux/device.h>
+#include "usb-platform.h"
+
+static void domain_release(struct kref *kref)
+{
+	struct usb_domain *udom = container_of(kref, struct usb_domain, kref);
+
+	WARN(!list_empty(&udom->connectors), "connectors list not empty\n");
+
+	kfree(udom);
+}
+
+void usb_put_domain(struct usb_domain *udom)
+{
+	if (udom)
+		kref_put(&udom->kref, domain_release);
+}
+EXPORT_SYMBOL_GPL(usb_put_domain);
+
+struct usb_domain *usb_get_domain(struct usb_domain *udom)
+{
+	if (udom)
+		kref_get(&udom->kref);
+	return udom;
+}
+EXPORT_SYMBOL_GPL(usb_get_domain);
+
+struct usb_domain *usb_create_domain(void)
+{
+	struct usb_domain *udom = kmalloc(sizeof(*udom), GFP_KERNEL);
+
+	if (!udom)
+		return NULL;
+
+	INIT_LIST_HEAD(&udom->connectors);
+	mutex_init(&udom->lock);
+	kref_init(&udom->kref);
+	return udom;
+}
+EXPORT_SYMBOL_GPL(usb_create_domain);
+
+static struct usb_connector *create_connector(struct usb_domain *udom,
+					      struct usb_port *port_dev,
+					      size_t pair_data)
+{
+	struct usb_connector *uconn;
+
+	uconn = kzalloc(sizeof(*uconn) + pair_data, GFP_KERNEL);
+	if (!uconn)
+		return NULL;
+
+	uconn->connect_type = port_dev->connect_type;
+	INIT_LIST_HEAD(&uconn->ports);
+	INIT_LIST_HEAD(&uconn->node);
+	uconn->domain = udom;
+
+	return uconn;
+}
+
+static void check_connector(struct usb_connector *uconn,
+			    struct usb_port *port_dev)
+{
+	if (uconn->connect_type != port_dev->connect_type) {
+		struct device *intf_dev = port_dev->dev.parent;
+
+		dev_info(intf_dev, "port%d inconsistent connect types %d:%d",
+			 port_dev->portnum, port_dev->connect_type,
+			 uconn->connect_type);
+		list_for_each_entry(port_dev, &uconn->ports, node)
+			port_dev->connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
+		uconn->connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
+	}
+}
+
+static void add_port_connector(struct usb_port *port_dev,
+			       struct usb_connector *uconn)
+{
+	list_add(&port_dev->node, &uconn->ports);
+	port_dev->connector = uconn;
+	check_connector(uconn, port_dev);
+}
+
+static void remove_port_connector(struct usb_port *port_dev)
+{
+	list_del_init(&port_dev->node);
+	port_dev->connector = NULL;
+}
+
+int usb_domain_pair_port(struct usb_domain *udom, struct usb_port *port_dev,
+			 void *data, size_t size)
+{
+	struct usb_connector *uconn;
+	int rc = -ENODEV;
+
+	if (!udom)
+		return 0;
+
+	mutex_lock(&udom->lock);
+	list_for_each_entry(uconn, &udom->connectors, node) {
+		if (memcmp(data, uconn->pair_data, size) != 0)
+			continue;
+		add_port_connector(port_dev, uconn);
+		port_dev = NULL;
+		break;
+	}
+	while (port_dev) {
+		uconn = create_connector(udom, port_dev, size);
+		if (!uconn) {
+			rc = -ENOMEM;
+			break;
+		}
+		usb_get_domain(udom);
+		memcpy(uconn->pair_data, data, size);
+		list_add(&uconn->node, &udom->connectors);
+		add_port_connector(port_dev, uconn);
+		break;
+	}
+	mutex_unlock(&udom->lock);
+
+	return rc;
+}
+
+void usb_domain_remove_port(struct usb_domain *udom, struct usb_port *port_dev)
+{
+	struct usb_connector *uconn;
+
+	if (!udom)
+		return;
+
+	usb_get_domain(udom);
+	mutex_lock(&udom->lock);
+	list_for_each_entry(uconn, &udom->connectors, node) {
+		struct usb_port *p;
+
+		list_for_each_entry(p, &uconn->ports, node)
+			if (p == port_dev) {
+				remove_port_connector(port_dev);
+				break;
+			}
+
+		if (!list_empty(&uconn->ports))
+			continue;
+
+		/* connector is empty, drop it from the domain */
+		list_del(&uconn->node);
+		usb_put_domain(udom);
+		kfree(uconn);
+		break;
+	}
+	mutex_unlock(&udom->lock);
+	usb_put_domain(udom);
+}
diff --git a/drivers/usb/core/usb-platform.h b/drivers/usb/core/usb-platform.h
new file mode 100644
index 000000000000..768bc1d4c831
--- /dev/null
+++ b/drivers/usb/core/usb-platform.h
@@ -0,0 +1,44 @@
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/usb.h>
+
+#include "hub.h"
+
+/**
+ * struct usb_domain - track pair ports by connector in this domain
+ * @connectors: list of struct usb_connector instances
+ * @lock: lock manipulation of 'connectors'
+ * @kref: ref count domain users
+ *
+ * Platform data describes the topology of the domain.  For example,
+ * ACPI may specify that port1 on hcd1 is a peer with port1 on hcd2
+ * where hcd1,2 share a common controller (as is the case with XHCI).
+ * Firmware stashes port to 'connector' relationships in this structure
+ * when it binds to the port devices.  (See: struct usb_connector)
+ */
+struct usb_domain {
+	struct list_head connectors;
+	struct mutex lock;
+	struct kref kref;
+};
+
+/**
+ * struct usb_connector - platform defined container of one or more ports
+ * @ports: platform defined ports that share this same connector
+ * @node: sibling connectors in the struct usb_domain
+ * @connect_type: unified connection type defined by the platform
+ * @domain: parent domain for these connectors
+ * @pair_data: opaque buffer for storing platform-specific port-pairing data
+ */
+struct usb_connector {
+	struct list_head ports;
+	struct list_head node;
+	enum usb_port_connect_type connect_type;
+	struct usb_domain *domain;
+	u8 pair_data[0];
+};
+
+int usb_domain_pair_port(struct usb_domain *udom, struct usb_port *port_dev,
+			 void *data, size_t size);
+void usb_domain_remove_port(struct usb_domain *udom, struct usb_port *port_dev);
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index b8dffd59eb25..eb8991ec1316 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -178,6 +178,11 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	struct xhci_hcd *xhci;
 	struct hc_driver *driver;
 	struct usb_hcd *hcd;
+	struct usb_domain *udom;
+
+	udom = usb_create_domain();
+	if (!udom)
+		return -ENOMEM;
 
 	driver = (struct hc_driver *)id->driver_data;
 	/* Register the USB 2.0 roothub.
@@ -186,10 +191,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	 * to say USB 2.0, but I'm not sure what the implications would be in
 	 * the other parts of the HCD code.
 	 */
-	retval = usb_hcd_pci_probe(dev, id);
+	retval = usb_hcd_pci_probe_domain(dev, udom, id);
 
 	if (retval)
-		return retval;
+		goto put_domain;
 
 	/* USB 2.0 roothub is stored in the PCI device now. */
 	hcd = dev_get_drvdata(&dev->dev);
@@ -200,6 +205,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		retval = -ENOMEM;
 		goto dealloc_usb2_hcd;
 	}
+	xhci->shared_hcd->domain = usb_get_domain(udom);
 
 	/* Set the xHCI pointer before xhci_pci_setup() (aka hcd_driver.reset)
 	 * is called by usb_add_hcd().
@@ -218,12 +224,18 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (xhci->quirks & XHCI_LPM_SUPPORT)
 		hcd_to_bus(xhci->shared_hcd)->root_hub->lpm_capable = 1;
 
+	/* now only the hcd(s) pin the domain */
+	usb_put_domain(udom);
+
 	return 0;
 
 put_usb3_hcd:
 	usb_put_hcd(xhci->shared_hcd);
 dealloc_usb2_hcd:
 	usb_hcd_pci_remove(dev);
+put_domain:
+	usb_put_domain(udom);
+
 	return retval;
 }
 
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index b8aba196f7f1..33e45e9ec81b 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -76,6 +76,7 @@ struct giveback_urb_bh {
 	struct usb_host_endpoint *completing_ep;
 };
 
+struct usb_domain;
 struct usb_hcd {
 
 	/*
@@ -83,6 +84,7 @@ struct usb_hcd {
 	 */
 	struct usb_bus		self;		/* hcd is-a bus */
 	struct kref		kref;		/* reference counter */
+	struct usb_domain	*domain;	/* platform port assocs */
 
 	const char		*product_desc;	/* product/vendor string */
 	int			speed;		/* Speed for this roothub.
@@ -412,6 +414,10 @@ extern int usb_hcd_alloc_bandwidth(struct usb_device *udev,
 		struct usb_host_interface *new_alt);
 extern int usb_hcd_get_frame_number(struct usb_device *udev);
 
+extern struct usb_domain *usb_create_domain(void);
+extern struct usb_domain *usb_get_domain(struct usb_domain *udom);
+extern void usb_put_domain(struct usb_domain *udom);
+
 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,
@@ -433,6 +439,9 @@ struct pci_dev;
 struct pci_device_id;
 extern int usb_hcd_pci_probe(struct pci_dev *dev,
 				const struct pci_device_id *id);
+extern int usb_hcd_pci_probe_domain(struct pci_dev *dev,
+				    struct usb_domain *udom,
+				    const struct pci_device_id *id);
 extern void usb_hcd_pci_remove(struct pci_dev *dev);
 extern void usb_hcd_pci_shutdown(struct pci_dev *dev);
 

--
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