summaryrefslogtreecommitdiff
path: root/drivers/usb/core/port.c
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-05-21 05:08:28 +0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-28 03:38:52 +0400
commitd8521afe35862f4fbe3ccd6ca37897c0a304edf3 (patch)
tree954c24e20f738a5b6fd0c7038bffdd5e8c71f211 /drivers/usb/core/port.c
parenta4204ff0bd576fc114357eed70e7c4e776ddf396 (diff)
downloadlinux-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.c73
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);
+}