Assume that the peer of a superspeed port is the port with the same id
on the shared_hcd root hub.  This identification scheme is required of
external hubs by the USB3 spec [1].  However, for root hubs, tier mismatch
may be in effect [2].  Tier mismatch can only be enumerated via platform
firmware.  For now, simply perform the nominal association.

[1]: usb 3.1 section 10.3.3
[2]: xhci 1.1 appendix D

Signed-off-by: Dan Williams <dan.j.willi...@intel.com>
---
 drivers/usb/core/hub.h  |    2 +
 drivers/usb/core/port.c |   98 ++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 94 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index baf5b48b79f7..d51feb68165b 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -81,6 +81,7 @@ struct usb_hub {
  * @child: usb device attached to the port
  * @dev: generic device interface
  * @port_owner: port's owner
+ * @peer: related usb2 and usb3 ports (share the same connector)
  * @connect_type: port's connect type
  * @portnum: port index num based one
  * @power_is_on: port's power state
@@ -90,6 +91,7 @@ struct usb_port {
        struct usb_device *child;
        struct device dev;
        struct dev_state *port_owner;
+       struct usb_port *peer;
        enum usb_port_connect_type connect_type;
        u8 portnum;
        unsigned power_is_on:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 9ae8a499b70f..d57fb01bbc1c 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -21,6 +21,7 @@
 
 #include "hub.h"
 
+static DEFINE_MUTEX(peer_lock);
 static const struct attribute_group *port_dev_group[];
 
 static ssize_t connect_type_show(struct device *dev,
@@ -146,9 +147,83 @@ struct device_type usb_port_device_type = {
        .pm =           &usb_port_pm_ops,
 };
 
+/*
+ * Set the default peer port for root hubs.  Platform firmware will have
+ * already set the peer if tier-mismatch is present.  Assumes the
+ * primary_hcd is registered first
+ */
+static struct usb_port *find_default_peer(struct usb_hub *hub, int port1)
+{
+       struct usb_device *hdev = hub->hdev;
+       struct usb_port *peer = NULL;
+
+       if (!hdev->parent) {
+               struct usb_hub *peer_hub;
+               struct usb_device *peer_hdev;
+               struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+               struct usb_hcd *peer_hcd = hcd->primary_hcd;
+
+               if (!peer_hcd || hcd == peer_hcd)
+                       return NULL;
+
+               peer_hdev = peer_hcd->self.root_hub;
+               peer_hub = usb_hub_to_struct_hub(peer_hdev);
+               if (peer_hub && port1 <= peer_hdev->maxchild)
+                       peer = peer_hub->ports[port1 - 1];
+       }
+
+       return peer;
+}
+
+static void link_peers(struct usb_port *left, struct usb_port *right)
+{
+       struct device *rdev;
+       struct device *ldev;
+
+       if (left->peer) {
+               right = left->peer;
+               ldev = left->dev.parent->parent;
+               rdev = right->dev.parent->parent;
+
+               WARN_ONCE(1, "%s port%d already peered with %s %d\n",
+                       dev_name(ldev), left->portnum, dev_name(rdev),
+                       right->portnum);
+               return;
+       } else if (right->peer) {
+               left = right->peer;
+               ldev = left->dev.parent->parent;
+               rdev = right->dev.parent->parent;
+
+               WARN_ONCE(1, "%s port%d already peered with %s %d\n",
+                       dev_name(rdev), right->portnum, dev_name(ldev),
+                       left->portnum);
+               return;
+       }
+
+       get_device(&right->dev);
+       left->peer = right;
+       get_device(&left->dev);
+       right->peer = left;
+}
+
+static void unlink_peers(struct usb_port *left, struct usb_port *right)
+{
+       struct device *rdev = right->dev.parent->parent;
+       struct device *ldev = left->dev.parent->parent;
+
+       WARN_ONCE(right->peer != left || left->peer != right,
+               "%s port%d and %s port%d are not peers?\n",
+               dev_name(ldev), left->portnum, dev_name(rdev), right->portnum);
+
+       put_device(&left->dev);
+       right->peer = NULL;
+       put_device(&right->dev);
+       left->peer = NULL;
+}
+
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
-       struct usb_port *port_dev = NULL;
+       struct usb_port *port_dev, *peer;
        int retval;
 
        port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
@@ -164,11 +239,16 @@ int usb_hub_create_port_device(struct usb_hub *hub, int 
port1)
        port_dev->dev.groups = port_dev_group;
        port_dev->dev.type = &usb_port_device_type;
        dev_set_name(&port_dev->dev, "port%d", port1);
-
        retval = device_register(&port_dev->dev);
        if (retval)
                goto error_register;
 
+       mutex_lock(&peer_lock);
+       peer = find_default_peer(hub, port1);
+       if (peer)
+               link_peers(port_dev, peer);
+       mutex_unlock(&peer_lock);
+
        pm_runtime_set_active(&port_dev->dev);
 
        /* It would be dangerous if user space couldn't prevent usb
@@ -190,9 +270,15 @@ exit:
        return retval;
 }
 
-void usb_hub_remove_port_device(struct usb_hub *hub,
-                                      int port1)
+void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
 {
-       device_unregister(&hub->ports[port1 - 1]->dev);
-}
+       struct usb_port *port_dev = hub->ports[port1 - 1];
+       struct usb_port *peer = port_dev->peer;
 
+       mutex_lock(&peer_lock);
+       if (peer)
+               unlink_peers(port_dev, peer);
+       mutex_unlock(&peer_lock);
+
+       device_unregister(&port_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