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.willi...@intel.com>
---
 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.willi...@intel.com>
+ *
+ * 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to