[PATCH v4 01/14] usb: assign default peer ports for root hubs

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

 



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.

[1]: usb 3.1 section 10.3.3
[2]: xhci 1.1 appendix D

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 babba885978d..548d327d89dd 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -36,11 +36,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 df629a310e44..975a0ad3a348 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -81,6 +81,7 @@ struct usb_hub {
  * @child: usb device attached 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




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

  Powered by Linux