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. Once the root hub is marked as unregistered we block attempts to walk the ->shared_hcd pointer to find a root-peer port. [1]: usb 3.1 section 10.3.3 [2]: xhci 1.1 appendix D Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hcd.c | 8 ++++ drivers/usb/core/hub.h | 2 + drivers/usb/core/port.c | 90 ++++++++++++++++++++++++++++++++++++++++++++--- drivers/usb/core/usb.h | 3 ++ 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 2518c3250750..e380b8a80830 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2778,9 +2778,17 @@ void usb_remove_hcd(struct usb_hcd *hcd) hcd->state = HC_STATE_QUIESCING; dev_dbg(hcd->self.controller, "roothub graceful disconnect\n"); + + /* + * Flush + disable peering operations, and atomically update the + * hcd state relative to other root hub state + * changes/evaluations + */ + mutex_lock(&usb_port_peer_mutex); spin_lock_irq (&hcd_root_hub_lock); hcd->rh_registered = 0; spin_unlock_irq (&hcd_root_hub_lock); + mutex_unlock(&usb_port_peer_mutex); #ifdef CONFIG_PM_RUNTIME cancel_work_sync(&hcd->wakeup_work); 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 afe0d894c907..d8bea408a2f2 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -21,6 +21,9 @@ #include "hub.h" +/* synchronize hub-port add/remove and peering operations */ +DEFINE_MUTEX(usb_port_peer_mutex); + static const struct attribute_group *port_dev_group[]; static ssize_t connect_type_show(struct device *dev, @@ -151,9 +154,71 @@ static struct device_driver usb_port_driver = { .owner = THIS_MODULE, }; +/* + * Set the default peer port for root hubs. 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 (!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; + + if (!peer_hcd || !peer_hcd->rh_registered + || !hcd->rh_registered + || 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) +{ + 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; +} + int usb_hub_create_port_device(struct usb_hub *hub, int port1) { - struct usb_port *port_dev = NULL; + struct usb_port *port_dev, *peer = NULL; int retval; port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); @@ -162,7 +227,6 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) goto exit; } - hub->ports[port1 - 1] = port_dev; port_dev->portnum = port1; port_dev->power_is_on = true; port_dev->dev.parent = hub->intfdev; @@ -171,7 +235,15 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) port_dev->dev.driver = &usb_port_driver; dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev), port1); + + mutex_lock(&usb_port_peer_mutex); + hub->ports[port1 - 1] = port_dev; retval = device_register(&port_dev->dev); + if (retval == 0) + peer = find_default_peer(hub, port1); + if (peer) + link_peers(port_dev, peer); + mutex_unlock(&usb_port_peer_mutex); if (retval) goto error_register; @@ -196,9 +268,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; + + mutex_lock(&usb_port_peer_mutex); + peer = port_dev->peer; + if (peer) + unlink_peers(port_dev, peer); + device_unregister(&port_dev->dev); + mutex_unlock(&usb_port_peer_mutex); } - diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 28f272884c0d..8a1acf59d498 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -157,6 +157,9 @@ static inline int is_usb_device_driver(struct device_driver *drv) for_devices; } +/* port peering lock shared between port, hub, and hcd */ +extern struct mutex usb_port_peer_mutex; + /* for labeling diagnostics */ extern const char *usbcore_name; -- 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