[PATCH v3 03/10] usb: find internal hub tier mismatch via acpi

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



ACPI identifies peer ports by setting their 'group_token' and 'group_position'
_PLD data to the same value.  If a platform has tier mismatch (see Appendix D
figure 131 in the xhci spec), ACPI can override the default peer port
association (for internal hubs).

Location data is cached as an opaque cookie in usb_port_location data.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.h      |    6 +++
 drivers/usb/core/port.c     |   96 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/usb-acpi.c |   35 +++++++++++++---
 drivers/usb/core/usb.h      |    2 +
 4 files changed, 131 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index b5efc713498e..2ba10798c943 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -76,6 +76,10 @@ struct usb_hub {
 	struct usb_port		**ports;
 };
 
+struct usb_port_location {
+	u32 cookie;
+};
+
 /**
  * struct usb port - kernel's representation of a usb port
  * @child: usb device attatched to the port
@@ -83,6 +87,7 @@ struct usb_hub {
  * @port_owner: port's owner
  * @peer: related usb2 and usb3 ports (share the same connector)
  * @connect_type: port's connect type
+ * @location: opaque representation of platform connector location
  * @portnum: port index num based one
  * @power_is_on: port's power state
  * @did_runtime_put: port has done pm_runtime_put().
@@ -93,6 +98,7 @@ struct usb_port {
 	struct dev_state *port_owner;
 	struct usb_port *peer;
 	enum usb_port_connect_type connect_type;
+	struct usb_port_location location;
 	u8 portnum;
 	unsigned power_is_on:1;
 	unsigned did_runtime_put:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 7fd22020d7ee..8fae3cd03305 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -149,11 +149,14 @@ struct device_type usb_port_device_type = {
 
 static struct usb_port *find_peer_port(struct usb_hub *hub, int port1)
 {
-	struct usb_device *hdev = hub->hdev;
+	struct usb_device *hdev = hub ? hub->hdev : NULL;
 	struct usb_device *peer_hdev;
 	struct usb_port *peer = NULL;
 	struct usb_hub *peer_hub;
 
+	if (!hub)
+		return 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
@@ -193,6 +196,97 @@ static struct usb_port *find_peer_port(struct usb_hub *hub, int port1)
 	return peer;
 }
 
+static void reset_peer(struct usb_port *port_dev, struct usb_port *peer)
+{
+	if (!peer)
+		return;
+
+	spin_lock(&peer_lock);
+	if (port_dev->peer)
+		put_device(&port_dev->peer->dev);
+	if (peer->peer)
+		put_device(&peer->peer->dev);
+	port_dev->peer = peer;
+	peer->peer = port_dev;
+	get_device(&peer->dev);
+	get_device(&port_dev->dev);
+	spin_unlock(&peer_lock);
+}
+
+/* assumes that location data is only set for external connectors and that
+ * external hubs never have tier mismatch
+ */
+static int redo_find_peer_port(struct device *dev, void *data)
+{
+	struct usb_port *port_dev = to_usb_port(dev);
+
+	if (is_usb_port(dev)) {
+		struct usb_device *hdev = to_usb_device(dev->parent->parent);
+		struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+		int port1 = port_dev->portnum;
+		struct usb_port *peer;
+
+		peer = find_peer_port(hub, port1);
+		reset_peer(port_dev, peer);
+	}
+
+	return device_for_each_child(dev, NULL, redo_find_peer_port);
+}
+
+static int pair_port(struct device *dev, void *data)
+{
+	struct usb_port *peer = data;
+	struct usb_port *port_dev = to_usb_port(dev);
+
+	if (!is_usb_port(dev)
+	    || port_dev->location.cookie != peer->location.cookie)
+		return device_for_each_child(dev, peer, pair_port);
+
+	dev_dbg(dev->parent->parent, "port%d peer = %s port%d (by location)\n",
+		port_dev->portnum, dev_name(peer->dev.parent->parent),
+		peer->portnum);
+	if (port_dev->peer != peer) {
+		/* Sigh, tier mismatch has invalidated our ancestry.
+		 * This should not be too onerous even in deep hub
+		 * topologies as we will discover tier mismatch early
+		 * (after platform internal hubs have been enumerated),
+		 * before external hubs are probed.
+		 */
+		reset_peer(port_dev, peer);
+		device_for_each_child(dev, NULL, redo_find_peer_port);
+	}
+
+	return true;
+}
+
+void usb_set_hub_port_location(struct usb_device *hdev, int port1,
+			       u32 cookie)
+{
+	struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+	struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+	struct usb_hcd *peer_hcd = hcd->shared_hcd;
+	struct usb_device *peer_hdev;
+	struct usb_port *port_dev;
+
+	if (cookie == 0)
+		return;
+
+	if (!hub)
+		return;
+
+	port_dev = hub->ports[port1 - 1];
+	port_dev->location.cookie = cookie;
+
+	/* see if a port with the same location data exists in a peer
+	 * usb domain
+	 */
+	if (!peer_hcd)
+		return;
+
+	peer_hdev = peer_hcd->self.root_hub;
+	device_for_each_child(&peer_hdev->dev, port_dev, pair_port);
+}
+
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
 	struct usb_port *port_dev, *peer;
diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c
index 4e243c37f17f..e957c5216ebe 100644
--- a/drivers/usb/core/usb-acpi.c
+++ b/drivers/usb/core/usb-acpi.c
@@ -18,7 +18,7 @@
 #include <linux/usb/hcd.h>
 #include <acpi/acpi_bus.h>
 
-#include "usb.h"
+#include "hub.h"
 
 /**
  * usb_acpi_power_manageable - check whether usb port has
@@ -42,6 +42,19 @@ bool usb_acpi_power_manageable(struct usb_device *hdev, int index)
 }
 EXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
 
+static void usb_acpi_check_port_peer(struct usb_device *hdev,
+				     acpi_handle *handle, int port1,
+				     struct acpi_pld_info *pld)
+{
+	if (!pld)
+		return;
+
+	#define USB_ACPI_LOCATION_VALID (1 << 31)
+	usb_set_hub_port_location(hdev, port1, USB_ACPI_LOCATION_VALID
+				  | pld->group_token << 8
+				  | pld->group_position);
+}
+
 /**
  * usb_acpi_set_power_state - control usb port's power via acpi power
  * resource
@@ -83,12 +96,11 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
 EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
 
 static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
-	acpi_handle handle, int port1)
+	acpi_handle handle, int port1, struct acpi_pld_info *pld)
 {
 	acpi_status status;
 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 	union acpi_object *upc;
-	struct acpi_pld_info *pld;
 	int ret = 0;
 
 	/*
@@ -99,8 +111,7 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
 	 * a usb device is directly hard-wired to the port. If no visible and
 	 * no connectable, the port would be not used.
 	 */
-	status = acpi_get_physical_device_location(handle, &pld);
-	if (ACPI_FAILURE(status))
+	if (!pld)
 		return -ENODEV;
 
 	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
@@ -122,7 +133,6 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
 		usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);
 
 out:
-	ACPI_FREE(pld);
 	kfree(upc);
 	return ret;
 }
@@ -179,6 +189,9 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
 			return -ENODEV;
 		return 0;
 	} else if (is_usb_port(dev)) {
+		struct acpi_pld_info *pld;
+		acpi_status status;
+
 		sscanf(dev_name(dev), "port%d", &port_num);
 		/* Get the struct usb_device point of port's hub */
 		udev = to_usb_device(dev->parent->parent);
@@ -209,7 +222,15 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
 			if (!*handle)
 				return -ENODEV;
 		}
-		usb_acpi_check_port_connect_type(udev, *handle, port_num);
+
+		status = acpi_get_physical_device_location(*handle, &pld);
+		if (ACPI_FAILURE(status))
+			pld = NULL;
+
+		usb_acpi_check_port_connect_type(udev, *handle, port_num, pld);
+		usb_acpi_check_port_peer(udev, *handle, port_num, pld);
+
+		ACPI_FREE(pld);
 	} else
 		return -ENODEV;
 
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index c49383669cd8..b80785106b27 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -184,6 +184,8 @@ extern enum usb_port_connect_type
 	usb_get_hub_port_connect_type(struct usb_device *hdev, int port1);
 extern void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
 	enum usb_port_connect_type type);
+extern void usb_set_hub_port_location(struct usb_device *hdev, int port1,
+				      u32 cookie);
 extern void usb_hub_adjust_deviceremovable(struct usb_device *hdev,
 		struct usb_hub_descriptor *desc);
 

--
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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux