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.

A new lock 'usb_port_peer_mutex' is introduced to synchronize port
device add/remove with peer lookups.  It protects peering against
changes to hcd->shared_hcd, hcd->self.root_hub, hdev->maxchild, and
port_dev->child pointers.

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

Cc: Alan Stern <st...@rowland.harvard.edu>
[alan: usb_port_peer_mutex locking scheme]
Acked-by: Alan Stern <st...@rowland.harvard.edu>
Signed-off-by: Dan Williams <dan.j.willi...@intel.com>
---
 drivers/usb/core/hcd.c  |   43 +++++++++++++++++++++++-----
 drivers/usb/core/hub.c  |   42 ++++++++++++++++++---------
 drivers/usb/core/hub.h  |    2 +
 drivers/usb/core/port.c |   73 ++++++++++++++++++++++++++++++++++++++++++++---
 drivers/usb/core/usb.h  |    1 +
 5 files changed, 134 insertions(+), 27 deletions(-)

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 9c4e2922b04d..6d12d7720632 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2454,11 +2454,13 @@ struct usb_hcd *usb_create_shared_hcd(const struct 
hc_driver *driver,
                mutex_init(hcd->bandwidth_mutex);
                dev_set_drvdata(dev, hcd);
        } else {
+               mutex_lock(&usb_port_peer_mutex);
                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_unlock(&usb_port_peer_mutex);
        }
 
        kref_init(&hcd->kref);
@@ -2510,18 +2512,25 @@ EXPORT_SYMBOL_GPL(usb_create_hcd);
  * deallocated.
  *
  * Make sure to only deallocate the bandwidth_mutex when the primary HCD is
- * freed.  When hcd_release() is called for the non-primary HCD, set the
- * primary_hcd's shared_hcd pointer to null (since the non-primary HCD will be
- * freed shortly).
+ * freed.  When hcd_release() is called for either hcd in a peer set
+ * invalidate the peer's ->shared_hcd and ->primary_hcd pointers to
+ * block new peering attempts
  */
-static void hcd_release (struct kref *kref)
+static void hcd_release(struct kref *kref)
 {
        struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref);
 
+       mutex_lock(&usb_port_peer_mutex);
        if (usb_hcd_is_primary_hcd(hcd))
                kfree(hcd->bandwidth_mutex);
-       else
-               hcd->shared_hcd->shared_hcd = NULL;
+       if (hcd->shared_hcd) {
+               struct usb_hcd *peer = hcd->shared_hcd;
+
+               peer->shared_hcd = NULL;
+               if (peer->primary_hcd == hcd)
+                       peer->primary_hcd = NULL;
+       }
+       mutex_unlock(&usb_port_peer_mutex);
        kfree(hcd);
 }
 
@@ -2589,6 +2598,21 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd,
        return 0;
 }
 
+/*
+ * Before we free this root hub, flush in-flight peering attempts
+ * and disable peer lookups
+ */
+static void usb_put_invalidate_rhdev(struct usb_hcd *hcd)
+{
+       struct usb_device *rhdev;
+
+       mutex_lock(&usb_port_peer_mutex);
+       rhdev = hcd->self.root_hub;
+       hcd->self.root_hub = NULL;
+       mutex_unlock(&usb_port_peer_mutex);
+       usb_put_dev(rhdev);
+}
+
 /**
  * usb_add_hcd - finish generic HCD structure initialization and register
  * @hcd: the usb_hcd structure to initialize
@@ -2649,7 +2673,9 @@ int usb_add_hcd(struct usb_hcd *hcd,
                retval = -ENOMEM;
                goto err_allocate_root_hub;
        }
+       mutex_lock(&usb_port_peer_mutex);
        hcd->self.root_hub = rhdev;
+       mutex_unlock(&usb_port_peer_mutex);
 
        switch (hcd->speed) {
        case HCD_USB11:
@@ -2758,7 +2784,7 @@ err_hcd_driver_start:
 err_request_irq:
 err_hcd_driver_setup:
 err_set_rh_speed:
-       usb_put_dev(hcd->self.root_hub);
+       usb_put_invalidate_rhdev(hcd);
 err_allocate_root_hub:
        usb_deregister_bus(&hcd->self);
 err_register_bus:
@@ -2838,7 +2864,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
                        free_irq(hcd->irq, hcd);
        }
 
-       usb_put_dev(hcd->self.root_hub);
        usb_deregister_bus(&hcd->self);
        hcd_buffer_destroy(hcd);
        if (hcd->remove_phy && hcd->phy) {
@@ -2846,6 +2871,8 @@ void usb_remove_hcd(struct usb_hcd *hcd)
                usb_put_phy(hcd->phy);
                hcd->phy = NULL;
        }
+
+       usb_put_invalidate_rhdev(hcd);
 }
 EXPORT_SYMBOL_GPL(usb_remove_hcd);
 
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 10c9b52ca0b9..c71f59125a08 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -55,6 +55,9 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
 
 static struct task_struct *khubd_task;
 
+/* synchronize hub-port add/remove and peering operations */
+DEFINE_MUTEX(usb_port_peer_mutex);
+
 /* cycle leds on hubs that aren't blinking for attention */
 static bool blinkenlights = 0;
 module_param (blinkenlights, bool, S_IRUGO);
@@ -1311,6 +1314,7 @@ static int hub_configure(struct usb_hub *hub,
        char *message = "out of memory";
        unsigned unit_load;
        unsigned full_load;
+       unsigned maxchild;
 
        hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
        if (!hub->buffer) {
@@ -1349,12 +1353,11 @@ static int hub_configure(struct usb_hub *hub,
                goto fail;
        }
 
-       hdev->maxchild = hub->descriptor->bNbrPorts;
-       dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild,
-               (hdev->maxchild == 1) ? "" : "s");
+       maxchild = hub->descriptor->bNbrPorts;
+       dev_info(hub_dev, "%d port%s detected\n", maxchild,
+                       (maxchild == 1) ? "" : "s");
 
-       hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *),
-                            GFP_KERNEL);
+       hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
        if (!hub->ports) {
                ret = -ENOMEM;
                goto fail;
@@ -1375,11 +1378,11 @@ static int hub_configure(struct usb_hub *hub,
                int     i;
                char    portstr[USB_MAXCHILDREN + 1];
 
-               for (i = 0; i < hdev->maxchild; i++)
+               for (i = 0; i < maxchild; i++)
                        portstr[i] = hub->descriptor->u.hs.DeviceRemovable
                                    [((i + 1) / 8)] & (1 << ((i + 1) % 8))
                                ? 'F' : 'R';
-               portstr[hdev->maxchild] = 0;
+               portstr[maxchild] = 0;
                dev_dbg(hub_dev, "compound device; port removable status: 
%s\n", portstr);
        } else
                dev_dbg(hub_dev, "standalone hub\n");
@@ -1491,7 +1494,7 @@ static int hub_configure(struct usb_hub *hub,
                if (hcd->power_budget > 0)
                        hdev->bus_mA = hcd->power_budget;
                else
-                       hdev->bus_mA = full_load * hdev->maxchild;
+                       hdev->bus_mA = full_load * maxchild;
                if (hdev->bus_mA >= full_load)
                        hub->mA_per_port = full_load;
                else {
@@ -1506,7 +1509,7 @@ static int hub_configure(struct usb_hub *hub,
                        hub->descriptor->bHubContrCurrent);
                hub->limited_power = 1;
 
-               if (remaining < hdev->maxchild * unit_load)
+               if (remaining < maxchild * unit_load)
                        dev_warn(hub_dev,
                                        "insufficient power available "
                                        "to use all downstream ports\n");
@@ -1574,15 +1577,19 @@ static int hub_configure(struct usb_hub *hub,
        if (hub->has_indicators && blinkenlights)
                hub->indicator[0] = INDICATOR_CYCLE;
 
-       for (i = 0; i < hdev->maxchild; i++) {
+       mutex_lock(&usb_port_peer_mutex);
+       for (i = 0; i < maxchild; i++) {
                ret = usb_hub_create_port_device(hub, i + 1);
                if (ret < 0) {
                        dev_err(hub->intfdev,
                                "couldn't create port%d device.\n", i + 1);
-                       hdev->maxchild = i;
-                       goto fail_keep_maxchild;
+                       break;
                }
        }
+       hdev->maxchild = i;
+       mutex_unlock(&usb_port_peer_mutex);
+       if (ret < 0)
+               goto fail;
 
        usb_hub_adjust_deviceremovable(hdev, hub->descriptor);
 
@@ -1590,8 +1597,6 @@ static int hub_configure(struct usb_hub *hub,
        return 0;
 
 fail:
-       hdev->maxchild = 0;
-fail_keep_maxchild:
        dev_err (hub_dev, "config failed, %s (err %d)\n",
                        message, ret);
        /* hub_disconnect() frees urb and descriptor */
@@ -1627,6 +1632,8 @@ static void hub_disconnect(struct usb_interface *intf)
        hub->error = 0;
        hub_quiesce(hub, HUB_DISCONNECT);
 
+       mutex_lock(&usb_port_peer_mutex);
+
        /* Avoid races with recursively_mark_NOTATTACHED() */
        spin_lock_irq(&device_state_lock);
        port1 = hdev->maxchild;
@@ -1637,6 +1644,8 @@ static void hub_disconnect(struct usb_interface *intf)
        for (; port1 > 0; --port1)
                usb_hub_remove_port_device(hub, port1);
 
+       mutex_unlock(&usb_port_peer_mutex);
+
        if (hub->hdev->speed == USB_SPEED_HIGH)
                highspeed_hubs--;
 
@@ -4586,6 +4595,8 @@ static void hub_port_connect_change(struct usb_hub *hub, 
int port1,
                 */
                status = 0;
 
+               mutex_lock(&usb_port_peer_mutex);
+
                /* We mustn't add new devices if the parent hub has
                 * been disconnected; we would race with the
                 * recursively_mark_NOTATTACHED() routine.
@@ -4596,14 +4607,17 @@ static void hub_port_connect_change(struct usb_hub 
*hub, int port1,
                else
                        port_dev->child = udev;
                spin_unlock_irq(&device_state_lock);
+               mutex_unlock(&usb_port_peer_mutex);
 
                /* Run it through the hoops (find a driver, etc) */
                if (!status) {
                        status = usb_new_device(udev);
                        if (status) {
+                               mutex_lock(&usb_port_peer_mutex);
                                spin_lock_irq(&device_state_lock);
                                port_dev->child = NULL;
                                spin_unlock_irq(&device_state_lock);
+                               mutex_unlock(&usb_port_peer_mutex);
                        }
                }
 
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 531a591a7b1e..0d36610af156 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -151,9 +151,66 @@ static struct device_driver usb_port_driver = {
        .owner = THIS_MODULE,
 };
 
+static void link_peers(struct usb_port *left, struct usb_port *right)
+{
+       if (left->peer == right && right->peer == left)
+               return;
+
+       if (left->peer || right->peer) {
+               struct usb_port *lpeer = left->peer;
+               struct usb_port *rpeer = right->peer;
+
+               WARN(1, "failed to peer %s and %s (%s -> %p) (%s -> %p)\n",
+                       dev_name(&left->dev), dev_name(&right->dev),
+                       dev_name(&left->dev), lpeer,
+                       dev_name(&right->dev), rpeer);
+               return;
+       }
+
+       left->peer = right;
+       right->peer = left;
+}
+
+static void unlink_peers(struct usb_port *left, struct usb_port *right)
+{
+       WARN(right->peer != left || left->peer != right,
+                       "%s and %s are not peers?\n",
+                       dev_name(&left->dev), dev_name(&right->dev));
+
+       right->peer = NULL;
+       left->peer = NULL;
+}
+
+/* set the default peer port for root hubs */
+static void find_and_link_peer(struct usb_hub *hub, int port1)
+{
+       struct usb_port *port_dev = hub->ports[port1 - 1], *peer;
+       struct usb_device *hdev = hub->hdev;
+
+       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->shared_hcd;
+
+               if (!peer_hcd)
+                       return;
+
+               peer_hdev = peer_hcd->self.root_hub;
+               peer_hub = usb_hub_to_struct_hub(peer_hdev);
+               if (!peer_hub || port1 > peer_hdev->maxchild)
+                       return;
+
+               peer = peer_hub->ports[port1 - 1];
+
+               if (peer)
+                       link_peers(port_dev, peer);
+       }
+}
+
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
-       struct usb_port *port_dev = NULL;
+       struct usb_port *port_dev;
        int retval;
 
        port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
@@ -175,6 +232,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int 
port1)
        if (retval)
                goto error_register;
 
+       find_and_link_peer(hub, port1);
+
        pm_runtime_set_active(&port_dev->dev);
 
        /*
@@ -197,9 +256,13 @@ 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;
 
+       peer = port_dev->peer;
+       if (peer)
+               unlink_peers(port_dev, peer);
+       device_unregister(&port_dev->dev);
+}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 981d3404fa55..793d003b7b9e 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -123,6 +123,7 @@ static inline int usb_set_usb2_hardware_lpm(struct 
usb_device *udev, int enable)
 #endif
 
 extern struct bus_type usb_bus_type;
+extern struct mutex usb_port_peer_mutex;
 extern struct device_type usb_device_type;
 extern struct device_type usb_if_device_type;
 extern struct device_type usb_ep_device_type;

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