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