[PATCH v6 part1 5/8] usb: assign usb3 external hub port peers

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

 



Given that root hub port peers are already established, external hub peer
ports can be determined by traversing the device topology:

1/ ascend to the parent hub and find the upstream port_dev

2/ walk ->peer to find the peer port

3/ descend to the peer hub via ->child

4/ find the port with the matching port id

Note that this assumes the port labeling scheme required by the
specification [1].

The usb_port_peer_mutex is held when marking hubs as disconnected
(meaning all child ports are invalid for peering) and when the ->child
pointer on a port_dev is marked NULL.  Strictly speaking it seems the
latter should be sufficient for protecting collisions with port peering.
However, this still includes the lock around setting hub->disconnected
for extra protection and documentation of the window for when a peer
relationship can be established.

[1]: usb3 3.1 section 10.3.3

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |   13 +++++++++++--
 drivers/usb/core/port.c |   26 ++++++++++++++++++++++++--
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index e5742b888ac8..c463c09c0d37 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1600,7 +1600,13 @@ static void hub_disconnect(struct usb_interface *intf)
 	struct usb_device *hdev = interface_to_usbdev(intf);
 	int port1;
 
-	/* Take the hub off the event list and don't let it be added again */
+	/*
+	 * 1/ Take the hub off the event list
+	 * 2/ Don't let it be added again
+	 * 3/ Flush in-progress peering / registration operations when
+	 *    setting ->disconnected
+	 */
+	mutex_lock(&usb_port_peer_mutex);
 	spin_lock_irq(&hub_event_lock);
 	if (!list_empty(&hub->event_list)) {
 		list_del_init(&hub->event_list);
@@ -1608,6 +1614,7 @@ static void hub_disconnect(struct usb_interface *intf)
 	}
 	hub->disconnected = 1;
 	spin_unlock_irq(&hub_event_lock);
+	mutex_unlock(&usb_port_peer_mutex);
 
 	/* Disconnect all children and quiesce the hub */
 	hub->error = 0;
@@ -2085,10 +2092,12 @@ void usb_disconnect(struct usb_device **pdev)
 	 */
 	release_devnum(udev);
 
-	/* Avoid races with recursively_mark_NOTATTACHED() */
+	/* Avoid races with recursively_mark_NOTATTACHED(), and peer lookups */
+	mutex_lock(&usb_port_peer_mutex);
 	spin_lock_irq(&device_state_lock);
 	*pdev = NULL;
 	spin_unlock_irq(&device_state_lock);
+	mutex_unlock(&usb_port_peer_mutex);
 
 	hub_free_dev(udev);
 
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index d8bea408a2f2..8ab5999e2166 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -161,14 +161,14 @@ static struct device_driver usb_port_driver = {
 static struct usb_port *find_default_peer(struct usb_hub *hub, int port1)
 {
 	struct usb_device *hdev = hub->hdev;
+	struct usb_device *peer_hdev;
 	struct usb_port *peer = NULL;
+	struct usb_hub *peer_hub;
 
 	if (!hub || hub->disconnected)
 		return 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;
 
@@ -181,6 +181,28 @@ static struct usb_port *find_default_peer(struct usb_hub *hub, int port1)
 		peer_hub = usb_hub_to_struct_hub(peer_hdev);
 		if (peer_hub && port1 <= peer_hdev->maxchild)
 			peer = peer_hub->ports[port1 - 1];
+	} else {
+		struct usb_port *upstream;
+		struct usb_device *parent = hdev->parent;
+		struct usb_hub *parent_hub = usb_hub_to_struct_hub(parent);
+
+		if (!parent_hub || parent_hub->disconnected)
+			return NULL;
+
+		upstream = parent_hub->ports[hdev->portnum - 1];
+		if (!upstream || !upstream->peer)
+			return NULL;
+
+		peer_hdev = upstream->peer->child;
+		if (!peer_hdev)
+			return NULL;
+
+		peer_hub = usb_hub_to_struct_hub(peer_hdev);
+		if (!peer_hub || peer_hub->disconnected
+				|| port1 > peer_hdev->maxchild)
+			return NULL;
+
+		peer = peer_hub->ports[port1 - 1];
 	}
 
 	return peer;

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