Assume that the peer of a superspeed port is the port with the same id on the shared_hcd root hub. This may be a lie if tier mismatch is in effect. Platform firmware can fix it after the port is registered. Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hub.c | 5 ---- drivers/usb/core/hub.h | 6 ++++ drivers/usb/core/port.c | 64 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 162e94dbed53..d86548edcc36 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -43,11 +43,6 @@ #define USB_VENDOR_GENESYS_LOGIC 0x05e3 #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 -static inline int hub_is_superspeed(struct usb_device *hdev) -{ - return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); -} - /* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, except that ->state can * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 4e4790dea343..b5efc713498e 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -81,6 +81,7 @@ struct usb_hub { * @child: usb device attatched 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; @@ -123,3 +125,7 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub, return hub_port_debounce(hub, port1, false); } +static inline int hub_is_superspeed(struct usb_device *hdev) +{ + return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS; +} diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 51542f852393..8d3ec2daf6fe 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -21,6 +21,7 @@ #include "hub.h" +DEFINE_SPINLOCK(peer_lock); static const struct attribute_group *port_dev_group[]; static ssize_t connect_type_show(struct device *dev, @@ -146,9 +147,37 @@ struct device_type usb_port_device_type = { .pm = &usb_port_pm_ops, }; +static struct usb_port *find_peer_port(struct usb_hub *hub, int port1) +{ + struct usb_device *hdev = hub->hdev; + struct usb_port *peer = NULL; + + /* set the default peer port for root hubs. Platform firmware + * can later fix this up if tier-mismatch is present. Assumes + * the primary_hcd is usb2.0 and registered first + */ + 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 (!hub_is_superspeed(hdev) + || WARN_ON_ONCE(!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; +} + 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,8 +193,21 @@ 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); + device_initialize(&port_dev->dev); + + peer = find_peer_port(hub, port1); + dev_dbg(&hub->hdev->dev, "port%d peer = %s\n", + port1, peer ? dev_name(peer->dev.parent->parent) : "[none]"); + if (peer) { + spin_lock(&peer_lock); + get_device(&peer->dev); + port_dev->peer = peer; + get_device(&port_dev->dev); + peer->peer = port_dev; + spin_unlock(&peer_lock); + } - retval = device_register(&port_dev->dev); + retval = device_add(&port_dev->dev); if (retval) goto error_register; @@ -188,9 +230,19 @@ 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; + + spin_lock(&peer_lock); + if (peer) { + peer->peer = NULL; + port_dev->peer = NULL; + put_device(&port_dev->dev); + put_device(&peer->dev); + } + spin_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