summaryrefslogtreecommitdiff
path: root/drivers/usb/core/port.c
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-05-20 18:08:40 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-27 16:38:52 -0700
commit3bfd659baec822f54e4acb0734669e671d853a35 (patch)
treec44ad6218e8b028e5595dd80e9c29213a8e96298 /drivers/usb/core/port.c
parent8b1ba80c59fb3e77f9e1761480617d5ea9ee159c (diff)
usb: find internal hub tier mismatch via acpi
ACPI identifies peer ports by setting their 'group_token' and 'group_position' _PLD data to the same value. If a platform has tier mismatch [1] , ACPI can override the default (USB3 defined) peer port association for internal hubs. External hubs follow the default peer association scheme. Location data is cached as an opaque cookie in usb_port_location data. Note that we only consider the group_token and group_position attributes from the _PLD data as ACPI specifies that group_token is a unique identifier. When we find port location data for a port then we assume that the firmware will also describe its peer port. This allows the implementation to only ever set the peer once. This leads to a question about what happens when a pm runtime event occurs while the peer associations are still resolving. Since we only ever set the peer information once, a USB3 port needs to be prevented from suspending while its ->peer pointer is NULL (implemented in a subsequent patch). There is always the possibility that firmware mis-identifies the ports, but there is not much the kernel can do in that case. [1]: xhci 1.1 appendix D figure 131 [2]: acpi 5 section 6.1.8 [alan]: don't do default peering when acpi data present Suggested-by: Alan Stern <stern@rowland.harvard.edu> 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.c56
1 files changed, 52 insertions, 4 deletions
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 9b7496b52f2a..aea54e8dfe47 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -188,8 +188,42 @@ static void unlink_peers(struct usb_port *left, struct usb_port *right)
}
/*
- * Set the default peer port for root hubs, or via the upstream peer
- * relationship for all other hubs
+ * For each usb hub device in the system check to see if it is in the
+ * peer domain of the given port_dev, and if it is check to see if it
+ * has a port that matches the given port by location
+ */
+static int match_location(struct usb_device *peer_hdev, void *p)
+{
+ int port1;
+ struct usb_hcd *hcd, *peer_hcd;
+ struct usb_port *port_dev = p, *peer;
+ struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev);
+ struct usb_device *hdev = to_usb_device(port_dev->dev.parent->parent);
+
+ if (!peer_hub)
+ return 0;
+
+ hcd = bus_to_hcd(hdev->bus);
+ peer_hcd = bus_to_hcd(peer_hdev->bus);
+ /* peer_hcd is provisional until we verify it against the known peer */
+ if (peer_hcd != hcd->shared_hcd)
+ return 0;
+
+ for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) {
+ peer = peer_hub->ports[port1 - 1];
+ if (peer && peer->location == port_dev->location) {
+ link_peers(port_dev, peer);
+ return 1; /* done */
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Find the peer port either via explicit platform firmware "location"
+ * data, the peer hcd for root hubs, or the upstream peer relationship
+ * for all other hubs.
*/
static void find_and_link_peer(struct usb_hub *hub, int port1)
{
@@ -198,7 +232,17 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
struct usb_device *peer_hdev;
struct usb_hub *peer_hub;
- if (!hdev->parent) {
+ /*
+ * If location data is available then we can only peer this port
+ * by a location match, not the default peer (lest we create a
+ * situation where we need to go back and undo a default peering
+ * when the port is later peered by location data)
+ */
+ if (port_dev->location) {
+ /* we link the peer in match_location() if found */
+ usb_for_each_dev(port_dev, match_location);
+ return;
+ } else if (!hdev->parent) {
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
struct usb_hcd *peer_hcd = hcd->shared_hcd;
@@ -225,8 +269,12 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
if (!peer_hub || port1 > peer_hdev->maxchild)
return;
+ /*
+ * we found a valid default peer, last check is to make sure it
+ * does not have location data
+ */
peer = peer_hub->ports[port1 - 1];
- if (peer)
+ if (peer && peer->location == 0)
link_peers(port_dev, peer);
}