diff options
author | Dan Williams <dan.j.williams@intel.com> | 2014-05-21 05:08:28 +0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-05-28 03:38:52 +0400 |
commit | d8521afe35862f4fbe3ccd6ca37897c0a304edf3 (patch) | |
tree | 954c24e20f738a5b6fd0c7038bffdd5e8c71f211 /drivers/usb/core/port.c | |
parent | a4204ff0bd576fc114357eed70e7c4e776ddf396 (diff) | |
download | linux-d8521afe35862f4fbe3ccd6ca37897c0a304edf3.tar.xz |
usb: assign default peer ports for root hubs
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.
A new lock 'usb_port_peer_mutex' is introduced to synchronize port
device add/remove with peer lookups. It protects peering against
changes to hcd->shared_hcd, hcd->self.root_hub, hdev->maxchild, and
port_dev->child pointers.
[1]: usb 3.1 section 10.3.3
[2]: xhci 1.1 appendix D
Cc: Alan Stern <stern@rowland.harvard.edu>
[alan: usb_port_peer_mutex locking scheme]
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core/port.c')
-rw-r--r-- | drivers/usb/core/port.c | 73 |
1 files changed, 68 insertions, 5 deletions
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 6a8999728cbf..5ecdbf31dfcb 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -157,9 +157,66 @@ static struct device_driver usb_port_driver = { .owner = THIS_MODULE, }; +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; +} + +/* set the default peer port for root hubs */ +static void find_and_link_peer(struct usb_hub *hub, int port1) +{ + struct usb_port *port_dev = hub->ports[port1 - 1], *peer; + struct usb_device *hdev = hub->hdev; + + 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->shared_hcd; + + if (!peer_hcd) + return; + + peer_hdev = peer_hcd->self.root_hub; + peer_hub = usb_hub_to_struct_hub(peer_hdev); + if (!peer_hub || port1 > peer_hdev->maxchild) + return; + + peer = peer_hub->ports[port1 - 1]; + + if (peer) + link_peers(port_dev, peer); + } +} + int usb_hub_create_port_device(struct usb_hub *hub, int port1) { - struct usb_port *port_dev = NULL; + struct usb_port *port_dev; int retval; port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); @@ -181,6 +238,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) if (retval) goto error_register; + find_and_link_peer(hub, port1); + pm_runtime_set_active(&port_dev->dev); /* @@ -203,9 +262,13 @@ 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; + peer = port_dev->peer; + if (peer) + unlink_peers(port_dev, peer); + device_unregister(&port_dev->dev); +} |