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.williams@xxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html