Linux USB
 help / color / mirror / Atom feed
* [PATCH v3 0/2] usb: core: Pair ports of different host controllers through their connector
@ 2026-11-13 16:39 Arun Iyengar via B4 Relay
  2026-11-13 16:39 ` [PATCH v3 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
  2026-11-13 16:39 ` [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
  0 siblings, 2 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-13 16:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Alan Stern
  Cc: linux-usb, linux-kernel, Mathias Nyman, Chen-Yu Tsai,
	Chen-Yu Tsai, Arun Iyengar

Chen-Yu Tsai pointed out that root hub graph ports use the raw xHCI
port number, as in DT and ACPI; v3 does that.

On the RK3588, USB 3.0 host 2 is a SuperSpeed-only xHCI and the USB 2.0
lines of its connector go to a separate EHCI. The USB core pairs the
SuperSpeed port with the dead USB 2.0 port of the same xHCI. On the
Orange Pi 5, whose USB 3 Type-A port has VBUS always on, a USB 3 device
present at boot connects at 480 Mb/s to the EHCI before the xHCI is up
and stays there.

Patch 1 pairs the two ports through a usb-connector whose halves lead
to different hubs or host controllers. Patch 2 resets such a device
once when the SuperSpeed root hub starts, so that it retries
SuperSpeed. A device that ignores the reset (SanDisk Ultra Fit) stays
at high speed.

The Orange Pi 5/5B DT goes to the Rockchip tree separately; its EHCI
"ports" node needs Chen-Yu's usb-hcd binding [1].
usb_of_get_connect_type() still looks up root hub ports by root hub
port number; Chen-Yu has fixes for those lookups. His M.2 series [2]
finds a hub port's graph port with fwnode_graph_get_port_by_id(); this
series does not depend on it.

Tested on an Orange Pi 5 (RK3588S) with a v7.3 based kernel: a USB 3
device present at boot came up at 5 Gb/s on host 2 in 4/4 boots and
stayed there across 8 s2idle cycles, xHCI rebinds and unbind races.
Type-C port pairing is unchanged. Not tested with an external USB 3 hub.

Developed with Claude (Anthropic), which wrote the code and the commit
messages. I reviewed every change.

[1] https://lore.kernel.org/all/20261014082037.1774672-2-wenst@chromium.org/
[2] https://lore.kernel.org/all/20260916075745.3549953-1-wenst@chromium.org/

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Changes in v3:
- Patch 1: find root hub ports in the graph by the raw controller port
  number (usb_hcd_find_raw_port_number()) on both sides (Chen-Yu Tsai)
- Patch 1: don't skip a connector peer whose connect_type is "not
  used"
- Shorter commit messages and comments; patch 2 code unchanged
- Link to v2: https://patch.msgid.link/20261112-usb-port-pairing-v2-0-45f5a1a29f1f@gmail.com

Changes in v2:
- Dropped v1 patch 1 (generic-ehci binding) for Chen-Yu Tsai's
  usb-hcd binding (Chen-Yu Tsai, Rob Herring)
- Patch 2: reset only when the SuperSpeed root hub starts, never a hub,
  only on USB 2.0 root hub ports of another controller; cancel in
  hub_quiesce(), runtime PM reference, freezable hub_wq (Sashiko)
- v1 patch 4 (DTS) split out (Krzysztof Kozlowski)
- Link to v1: https://patch.msgid.link/20261110-usb-port-pairing-v1-0-9307e29d2ed2@gmail.com

---
Arun Iyengar (2):
      usb: core: Pair ports of different hubs through their connector
      usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer

 drivers/usb/core/hub.c  | 109 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/hub.h  |   3 ++
 drivers/usb/core/of.c   |  98 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/port.c |  67 +++++++++++++++++++++++++++--
 include/linux/usb/of.h  |   8 ++++
 5 files changed, 281 insertions(+), 4 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261110-usb-port-pairing-af0f66f0ab2e

Best regards,
--  
Arun Iyengar <aiyengar1990@gmail.com>



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/2] usb: core: Pair ports of different hubs through their connector
  2026-11-13 16:39 [PATCH v3 0/2] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
@ 2026-11-13 16:39 ` Arun Iyengar via B4 Relay
  2026-11-13 17:20   ` sashiko-bot
  2026-11-13 16:39 ` [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
  1 sibling, 1 reply; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-13 16:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Alan Stern
  Cc: linux-usb, linux-kernel, Mathias Nyman, Chen-Yu Tsai,
	Chen-Yu Tsai, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

The USB core pairs the USB 2.0 and SuperSpeed ports of a USB 3 connector
by port number within one xHCI, or by ACPI location. On the RK3588, USB
3.0 host 2 is SuperSpeed-only and the connector's USB 2.0 lines go to a
separate EHCI: the SuperSpeed port is paired with a dead USB 2.0 port and
the EHCI port has no peer.

A usb-connector has its USB 2.0 lines on graph port 0 and its SuperSpeed
lanes on graph port 1. When the two lead to different hubs or host
controllers, pair the two ports through the connector. Root hub ports are
found in the graph by the controller's port number, as in ACPI.

This also applies the peer runtime PM rules to such connectors.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/usb/core/hub.h  |  2 +
 drivers/usb/core/of.c   | 98 +++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/port.c | 67 +++++++++++++++++++++++++++++++--
 include/linux/usb/of.h  |  8 ++++
 4 files changed, 171 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 9ebc5ef54a32..6b55216c7da0 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -97,6 +97,7 @@ struct usb_hub {
  * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted.
  * @early_stop: whether port initialization will be stopped earlier.
  * @ignore_event: whether events of the port are ignored.
+ * @connector_peer: the peer is chosen by a connector, not by port number.
  */
 struct usb_port {
 	struct usb_device *child;
@@ -118,6 +119,7 @@ struct usb_port {
 	unsigned int is_superspeed:1;
 	unsigned int usb3_lpm_u1_permit:1;
 	unsigned int usb3_lpm_u2_permit:1;
+	unsigned int connector_peer:1;
 };
 
 #define to_usb_port(_dev) \
diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c
index a6ee20d8774e..1b26d2083525 100644
--- a/drivers/usb/core/of.c
+++ b/drivers/usb/core/of.c
@@ -145,6 +145,104 @@ enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int p
 }
 EXPORT_SYMBOL_GPL(usb_of_get_connect_type);
 
+static bool usb_of_is_connector(const struct device_node *np)
+{
+	return of_device_is_compatible(np, "usb-a-connector") ||
+	       of_device_is_compatible(np, "usb-b-connector") ||
+	       of_device_is_compatible(np, "usb-c-connector");
+}
+
+/*
+ * Return the node owning the far end of @con's endpoint on @con_port, and in
+ * @port the graph port of that end.
+ */
+static struct device_node *usb_of_connector_remote(struct device_node *con,
+						   u32 con_port, u32 *port)
+{
+	struct device_node *ep, *remote;
+	struct of_endpoint endpoint;
+
+	ep = of_graph_get_endpoint_by_regs(con, con_port, -1);
+	if (!ep)
+		return NULL;
+
+	remote = of_graph_get_remote_endpoint(ep);
+	of_node_put(ep);
+	if (!remote)
+		return NULL;
+
+	if (of_graph_parse_endpoint(remote, &endpoint)) {
+		of_node_put(remote);
+		return NULL;
+	}
+	*port = endpoint.port;
+
+	ep = remote;
+	remote = of_graph_get_port_parent(ep);
+	of_node_put(ep);
+
+	return remote;
+}
+
+/**
+ * usb_of_get_connector_peer() - find a port's peer through its connector
+ * @hub: hub to which the port belongs
+ * @port: graph port of the port: the controller's port number on a root hub,
+ *	  else the hub port number
+ * @superspeed: whether @hub is a SuperSpeed hub
+ * @peer_port: returns the graph port of the peer
+ *
+ * A USB connector has its USB 2.0 lines on graph port 0 and its SuperSpeed
+ * lanes on graph port 1. Some SoCs route the two to different host
+ * controllers; the two ports can then only be paired through the connector.
+ *
+ * Return: the node of the hub or host controller with the peer port, with its
+ * refcount incremented, if @port is the @superspeed half of a connector whose
+ * other half leads to a different node. NULL otherwise.
+ */
+struct device_node *usb_of_get_connector_peer(struct usb_device *hub, u32 port,
+					      bool superspeed, u32 *peer_port)
+{
+	struct device_node *np = hub->dev.of_node;
+	struct device_node *ep, *con, *remote, *peer = NULL;
+	u32 remote_port;
+
+	if (!np || !of_graph_is_present(np))
+		return NULL;
+
+	ep = of_graph_get_endpoint_by_regs(np, port, -1);
+	if (!ep)
+		return NULL;
+
+	con = of_graph_get_remote_port_parent(ep);
+	of_node_put(ep);
+	if (!con)
+		return NULL;
+
+	if (!usb_of_is_connector(con) || !of_device_is_available(con))
+		goto out;
+
+	/* The connector's half for our speed must lead back to this port */
+	remote = usb_of_connector_remote(con, superspeed ? 1 : 0, &remote_port);
+	if (!remote)
+		goto out;
+	of_node_put(remote);
+	if (remote != np || remote_port != port)
+		goto out;
+
+	/* Peers on the same node are paired without the connector's help */
+	peer = usb_of_connector_remote(con, superspeed ? 0 : 1, peer_port);
+	if (peer && (peer == np || !*peer_port ||
+		     !of_device_is_available(peer))) {
+		of_node_put(peer);
+		peer = NULL;
+	}
+out:
+	of_node_put(con);
+	return peer;
+}
+EXPORT_SYMBOL_GPL(usb_of_get_connector_peer);
+
 /**
  * usb_of_get_interface_node() - get a USB interface node
  * @udev: USB device of interface
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index b4452b665f59..6a65dc6f14e7 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -649,10 +649,52 @@ static int match_location(struct usb_device *peer_hdev, void *p)
 	return 0;
 }
 
+/* Root hub graph ports use the controller's port number, as in ACPI */
+static u32 usb_port_graph_id(struct usb_device *hdev, int port1)
+{
+	if (hdev->parent)
+		return port1;
+	return usb_hcd_find_raw_port_number(bus_to_hcd(hdev->bus), port1);
+}
+
+struct usb_connector_peer {
+	struct usb_port *port_dev;
+	struct device_node *np;
+	u32 port;
+};
+
+/* Link the port to @peer_hdev's port at graph port cp->port, if it has one */
+static int match_connector_peer(struct usb_device *peer_hdev, void *p)
+{
+	struct usb_connector_peer *cp = p;
+	struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev);
+	struct usb_port *peer;
+	int port1;
+
+	if (!peer_hub || peer_hdev->dev.of_node != cp->np ||
+	    hub_is_superspeed(peer_hdev) == cp->port_dev->is_superspeed)
+		return 0;
+
+	for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) {
+		if (usb_port_graph_id(peer_hdev, port1) == cp->port)
+			break;
+	}
+	if (port1 > peer_hdev->maxchild)
+		return 0;
+
+	/* No connect_type check: the connector shows the port is used */
+	peer = peer_hub->ports[port1 - 1];
+	if (!peer)
+		return 0;
+
+	link_peers_report(cp->port_dev, peer);
+	return 1; /* done */
+}
+
 /*
  * 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.
+ * data, a connector described in the device tree, 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)
 {
@@ -660,6 +702,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
 	struct usb_device *hdev = hub->hdev;
 	struct usb_device *peer_hdev;
 	struct usb_hub *peer_hub;
+	struct usb_connector_peer cp;
 
 	/*
 	 * If location data is available then we can only peer this port
@@ -671,7 +714,23 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
 		/* we link the peer in match_location() if found */
 		usb_for_each_dev(port_dev, match_location);
 		return;
-	} else if (!hdev->parent) {
+	}
+
+	/*
+	 * If a connector routes the two halves of this port to different
+	 * hubs or host controllers, the connector alone decides the peer.
+	 */
+	cp.np = usb_of_get_connector_peer(hdev, usb_port_graph_id(hdev, port1),
+					  port_dev->is_superspeed, &cp.port);
+	if (cp.np) {
+		cp.port_dev = port_dev;
+		port_dev->connector_peer = 1;
+		usb_for_each_dev(&cp, match_connector_peer);
+		of_node_put(cp.np);
+		return;
+	}
+
+	if (!hdev->parent) {
 		struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
 		struct usb_hcd *peer_hcd = hcd->shared_hcd;
 
@@ -703,7 +762,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
 	 * does not have location data
 	 */
 	peer = peer_hub->ports[port1 - 1];
-	if (peer && peer->location == 0)
+	if (peer && peer->location == 0 && !peer->connector_peer)
 		link_peers_report(port_dev, peer);
 }
 
diff --git a/include/linux/usb/of.h b/include/linux/usb/of.h
index de42f14bd280..4be581bd2758 100644
--- a/include/linux/usb/of.h
+++ b/include/linux/usb/of.h
@@ -20,6 +20,8 @@ int of_usb_update_otg_caps(struct device_node *np,
 			struct usb_otg_caps *otg_caps);
 enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1);
 struct device_node *usb_of_get_device_node(struct usb_device *hub, int port1);
+struct device_node *usb_of_get_connector_peer(struct usb_device *hub, u32 port,
+					      bool superspeed, u32 *peer_port);
 bool usb_of_has_combined_node(struct usb_device *udev);
 struct device_node *usb_of_get_interface_node(struct usb_device *udev,
 		u8 config, u8 ifnum);
@@ -49,6 +51,12 @@ usb_of_get_device_node(struct usb_device *hub, int port1)
 {
 	return NULL;
 }
+static inline struct device_node *
+usb_of_get_connector_peer(struct usb_device *hub, u32 port, bool superspeed,
+			  u32 *peer_port)
+{
+	return NULL;
+}
 static inline bool usb_of_has_combined_node(struct usb_device *udev)
 {
 	return false;

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
  2026-11-13 16:39 [PATCH v3 0/2] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
  2026-11-13 16:39 ` [PATCH v3 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
@ 2026-11-13 16:39 ` Arun Iyengar via B4 Relay
  2026-11-13 17:08   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-13 16:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Alan Stern
  Cc: linux-usb, linux-kernel, Mathias Nyman, Chen-Yu Tsai,
	Chen-Yu Tsai, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

A SuperSpeed device that finds no SuperSpeed link at power-up connects at
high speed, and tries SuperSpeed again only after a VBUS cycle or a USB
2.0 bus reset. When a connector's two halves are on different host
controllers and VBUS is always on, as on the Orange Pi 5 USB 3 Type-A
port, a device present at boot connects to the EHCI before the
SuperSpeed-only xHCI is up and stays at 480 Mb/s.

When such a SuperSpeed root hub starts, reset once a SuperSpeed-capable
device that sits at high speed on a USB 2.0 peer port. Hubs are skipped:
the USB 2.0 half of a USB 3 hub stays at high speed. Resume and reset of
the root hub don't do this; a device suspended with the system can't be
reset.

The device leaves the USB 2.0 port, so whatever uses it by then (a
mounted disk) sees a disconnect.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/usb/core/hub.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/hub.h |   1 +
 2 files changed, 110 insertions(+)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 199d3f535a89..6bbe5fe2c0b5 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1076,6 +1076,105 @@ int usb_remove_device(struct usb_device *udev)
 	return 0;
 }
 
+/*
+ * A device present at boot may have connected at high speed to the USB 2.0
+ * peer before this SuperSpeed root hub was up; a USB 2.0 reset makes it retry.
+ */
+static void hub_reset_peer_child(struct usb_device *peer_hdev, int peer_port1)
+{
+	struct usb_hub *peer_hub;
+	struct usb_device *udev;
+
+	/* Wait for an enumeration in progress on the peer port to finish */
+	usb_lock_device(peer_hdev);
+	peer_hub = usb_hub_to_struct_hub(peer_hdev);
+	if (!peer_hub || peer_port1 > peer_hdev->maxchild)
+		goto out;
+
+	udev = peer_hub->ports[peer_port1 - 1]->child;
+	if (!udev || udev->speed != USB_SPEED_HIGH)
+		goto out;
+
+	/*
+	 * The USB 2.0 half of a USB 3 hub stays at high speed next to its
+	 * SuperSpeed half, and resetting it would disconnect its children.
+	 */
+	if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
+		goto out;
+
+	usb_lock_device(udev);
+	if (udev->state != USB_STATE_NOTATTACHED &&
+	    udev->bos && udev->bos->ss_cap) {
+		dev_info(&udev->dev,
+			 "SuperSpeed peer port available, resetting\n");
+		usb_reset_device(udev);
+	}
+	usb_unlock_device(udev);
+out:
+	usb_unlock_device(peer_hdev);
+}
+
+static void hub_peer_reset_work(struct work_struct *ws)
+{
+	struct usb_hub *hub = container_of(ws, struct usb_hub, peer_reset_work);
+	struct usb_device *hdev = hub->hdev;
+	int port1;
+
+	for (port1 = 1; ; port1++) {
+		struct usb_device *peer_hdev = NULL;
+		struct usb_port *port_dev;
+		int peer_port1 = 0;
+
+		mutex_lock(&usb_port_peer_mutex);
+		if (port1 > hdev->maxchild) {
+			mutex_unlock(&usb_port_peer_mutex);
+			break;
+		}
+		port_dev = hub->ports[port1 - 1];
+		if (port_dev->connector_peer && port_dev->peer) {
+			peer_hdev = to_usb_device(port_dev->peer->dev.parent->parent);
+			peer_port1 = port_dev->peer->portnum;
+			/* Only root hub ports of another host controller */
+			if (peer_hdev->parent ||
+			    peer_hdev->bus->controller == hdev->bus->controller)
+				peer_hdev = NULL;
+			else
+				usb_get_dev(peer_hdev);
+		}
+		mutex_unlock(&usb_port_peer_mutex);
+
+		if (peer_hdev) {
+			hub_reset_peer_child(peer_hdev, peer_port1);
+			usb_put_dev(peer_hdev);
+		}
+	}
+
+	usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
+	hub_put(hub);
+}
+
+/* Called when a SuperSpeed root hub has started */
+static void hub_queue_peer_reset(struct usb_hub *hub)
+{
+	struct usb_interface *intf = to_usb_interface(hub->intfdev);
+	int port1;
+
+	for (port1 = 1; port1 <= hub->hdev->maxchild; port1++) {
+		if (hub->ports[port1 - 1]->connector_peer)
+			break;
+	}
+	if (port1 > hub->hdev->maxchild)
+		return;
+
+	/* Keep the hub active until the work is done; hub_quiesce() cancels it */
+	usb_autopm_get_interface_no_resume(intf);
+	hub_get(hub);
+	if (!queue_work(hub_wq, &hub->peer_reset_work)) {
+		usb_autopm_put_interface_async(intf);
+		hub_put(hub);
+	}
+}
+
 enum hub_activation_type {
 	HUB_INIT, HUB_INIT2, HUB_INIT3,		/* INITs must come first */
 	HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
@@ -1346,6 +1445,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
 	/* Scan all ports that need attention */
 	kick_hub_wq(hub);
+
+	/* Let devices stuck at high speed on a connector peer move over */
+	if (!hdev->parent && hub_is_superspeed(hdev) &&
+	    (type == HUB_INIT2 || type == HUB_INIT3))
+		hub_queue_peer_reset(hub);
  abort:
 	if (type == HUB_INIT2 || type == HUB_INIT3) {
 		/* Allow autosuspend if it was suppressed */
@@ -1419,6 +1523,10 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
 	/* Stop hub_wq and related activity */
 	timer_delete_sync(&hub->irq_urb_retry);
 	flush_delayed_work(&hub->post_resume_work);
+	if (cancel_work_sync(&hub->peer_reset_work)) {
+		usb_autopm_put_interface_no_suspend(to_usb_interface(hub->intfdev));
+		hub_put(hub);
+	}
 	usb_kill_urb(hub->urb);
 	if (hub->has_indicators)
 		cancel_delayed_work_sync(&hub->leds);
@@ -1978,6 +2086,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
 	INIT_DELAYED_WORK(&hub->leds, led_work);
 	INIT_DELAYED_WORK(&hub->init_work, NULL);
 	INIT_DELAYED_WORK(&hub->post_resume_work, hub_post_resume);
+	INIT_WORK(&hub->peer_reset_work, hub_peer_reset_work);
 	INIT_WORK(&hub->events, hub_event);
 	INIT_LIST_HEAD(&hub->onboard_devs);
 	spin_lock_init(&hub->irq_urb_lock);
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 6b55216c7da0..1dccfd5ea4ae 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -71,6 +71,7 @@ struct usb_hub {
 	struct delayed_work	leds;
 	struct delayed_work	init_work;
 	struct delayed_work	post_resume_work;
+	struct work_struct	peer_reset_work;
 	struct work_struct      events;
 	spinlock_t		irq_urb_lock;
 	struct timer_list	irq_urb_retry;

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
  2026-11-13 16:39 ` [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
@ 2026-11-13 17:08   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-13 17:08 UTC (permalink / raw)
  To: Arun Iyengar; +Cc: linux-usb

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] usb: hub: hub_peer_reset_work() ignores quiescing state during
  teardown
--

commit 292ab4e7add6e385ccf460779fb12521a3d0b6e7
Author: Arun Iyengar <aiyengar1990@gmail.com>

usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer

This commit schedules work when a SuperSpeed root hub initializes to reset
any SuperSpeed-capable device connected at high speed to a peer USB 2.0
root hub port on another host controller so it can reconnect at
SuperSpeed.

> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 199d3f535a891..6bbe5fe2c0b52 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -1076,6 +1076,105 @@ int usb_remove_device(struct usb_device *udev)
>  	return 0;
>  }
>  
> +/*
> + * A device present at boot may have connected at high speed to the USB 2.0
> + * peer before this SuperSpeed root hub was up; a USB 2.0 reset makes it retry.
> + */
> +static void hub_reset_peer_child(struct usb_device *peer_hdev, int peer_port1)
> +{
> +	struct usb_hub *peer_hub;
> +	struct usb_device *udev;
> +
> +	/* Wait for an enumeration in progress on the peer port to finish */
> +	usb_lock_device(peer_hdev);
> +	peer_hub = usb_hub_to_struct_hub(peer_hdev);
> +	if (!peer_hub || peer_port1 > peer_hdev->maxchild)
> +		goto out;
> +
> +	udev = peer_hub->ports[peer_port1 - 1]->child;
> +	if (!udev || udev->speed != USB_SPEED_HIGH)
> +		goto out;
> +
> +	/*
> +	 * The USB 2.0 half of a USB 3 hub stays at high speed next to its
> +	 * SuperSpeed half, and resetting it would disconnect its children.
> +	 */
> +	if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
> +		goto out;
> +
> +	usb_lock_device(udev);
> +	if (udev->state != USB_STATE_NOTATTACHED &&
> +	    udev->bos && udev->bos->ss_cap) {
> +		dev_info(&udev->dev,
> +			 "SuperSpeed peer port available, resetting\n");
> +		usb_reset_device(udev);
> +	}
> +	usb_unlock_device(udev);
> +out:
> +	usb_unlock_device(peer_hdev);
> +}
> +
> +static void hub_peer_reset_work(struct work_struct *ws)
> +{
> +	struct usb_hub *hub = container_of(ws, struct usb_hub, peer_reset_work);
> +	struct usb_device *hdev = hub->hdev;
> +	int port1;
> +
> +	for (port1 = 1; ; port1++) {
> +		struct usb_device *peer_hdev = NULL;
> +		struct usb_port *port_dev;
> +		int peer_port1 = 0;
> +
> +		mutex_lock(&usb_port_peer_mutex);
> +		if (port1 > hdev->maxchild) {
> +			mutex_unlock(&usb_port_peer_mutex);
> +			break;
> +		}
> +		port_dev = hub->ports[port1 - 1];
> +		if (port_dev->connector_peer && port_dev->peer) {
> +			peer_hdev = to_usb_device(port_dev->peer->dev.parent->parent);
> +			peer_port1 = port_dev->peer->portnum;
> +			/* Only root hub ports of another host controller */
> +			if (peer_hdev->parent ||
> +			    peer_hdev->bus->controller == hdev->bus->controller)
> +				peer_hdev = NULL;
> +			else
> +				usb_get_dev(peer_hdev);
> +		}
> +		mutex_unlock(&usb_port_peer_mutex);
> +
> +		if (peer_hdev) {
> +			hub_reset_peer_child(peer_hdev, peer_port1);
> +			usb_put_dev(peer_hdev);
> +		}
> +	}

[Severity: Low]
Should hub_peer_reset_work() and hub_reset_peer_child() check
hub->quiescing?

hub_disconnect() only zeroes hdev->maxchild under usb_port_peer_mutex
after hub_quiesce() has finished waiting in
cancel_work_sync(&hub->peer_reset_work).  If the SuperSpeed root hub is
quiesced or disconnected while hub_peer_reset_work() is running (for
instance, while hub_reset_peer_child() is blocked on
usb_lock_device(peer_hdev) waiting for USB 2.0 enumeration to finish), the
work item will still reset the USB 2.0 peer devices across all remaining
ports to migrate them toward a SuperSpeed root hub that is being torn
down.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-usb-port-pairing-v3-0-d6c6edb4341c@gmail.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 1/2] usb: core: Pair ports of different hubs through their connector
  2026-11-13 16:39 ` [PATCH v3 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
@ 2026-11-13 17:20   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-13 17:20 UTC (permalink / raw)
  To: Arun Iyengar; +Cc: linux-usb

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] usb: core: port numbering mismatch in usb_port_graph_id() for
  controller DT
- [Medium] usb: core: wrong port index passed to
  usb_of_get_connect_type() on root hubs
--

commit 6078d04cbe13cbfa47a3ee5f362dcccb9bc5fd1f
Author: Arun Iyengar <aiyengar1990@gmail.com>

usb: core: Pair ports of different hubs through their connector

This commit pairs USB 2.0 and SuperSpeed ports routed to different hubs or
host controllers by traversing their shared usb-connector OF graph node.

> diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c
> index a6ee20d8774e3..1b26d20835252 100644
> --- a/drivers/usb/core/of.c
> +++ b/drivers/usb/core/of.c
> @@ -145,6 +145,104 @@ enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int p
[ ... ]
> +struct device_node *usb_of_get_connector_peer(struct usb_device *hub, u32 port,
> +					      bool superspeed, u32 *peer_port)
> +{
[ ... ]
> +	/* Peers on the same node are paired without the connector's help */
> +	peer = usb_of_connector_remote(con, superspeed ? 0 : 1, peer_port);
> +	if (peer && (peer == np || !*peer_port ||
> +		     !of_device_is_available(peer))) {
> +		of_node_put(peer);
> +		peer = NULL;
> +	}
> +out:
> +	of_node_put(con);
> +	return peer;
> +}
> +EXPORT_SYMBOL_GPL(usb_of_get_connector_peer);
> +
>  /**
>   * usb_of_get_interface_node() - get a USB interface node
>   * @udev: USB device of interface
> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
> index b4452b665f591..6a65dc6f14e7c 100644
> --- a/drivers/usb/core/port.c
> +++ b/drivers/usb/core/port.c
> @@ -649,10 +649,52 @@ static int match_location(struct usb_device *peer_hdev, void *p)
>  	return 0;
>  }
>  
> +/* Root hub graph ports use the controller's port number, as in ACPI */
> +static u32 usb_port_graph_id(struct usb_device *hdev, int port1)
> +{
> +	if (hdev->parent)
> +		return port1;
> +	return usb_hcd_find_raw_port_number(bus_to_hcd(hdev->bus), port1);
> +}
> +
> +struct usb_connector_peer {
> +	struct usb_port *port_dev;
> +	struct device_node *np;
> +	u32 port;
> +};
> +
> +/* Link the port to @peer_hdev's port at graph port cp->port, if it has one */
> +static int match_connector_peer(struct usb_device *peer_hdev, void *p)
> +{
> +	struct usb_connector_peer *cp = p;
> +	struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev);
> +	struct usb_port *peer;
> +	int port1;
> +
> +	if (!peer_hub || peer_hdev->dev.of_node != cp->np ||
> +	    hub_is_superspeed(peer_hdev) == cp->port_dev->is_superspeed)
> +		return 0;
> +
> +	for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) {
> +		if (usb_port_graph_id(peer_hdev, port1) == cp->port)
> +			break;
> +	}

[Severity: High]
Does usb_port_graph_id() mismatch the OF graph port numbers used by USB
controller Device Tree bindings?

On DWC3/xHCI controllers, usb_hcd_find_raw_port_number() returns 1 for the
High-Speed root hub port and 2 for the SuperSpeed root hub port, and
usb_of_get_connector_peer() rejects *peer_port == 0.  However, controller DT
bindings such as Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml
define port@0 as the High-Speed bus and port@1 as the SuperSpeed bus (or use
a single unnumbered port node parsed as port 0).

On in-tree boards such as arch/arm64/boot/dts/rockchip/rk3576-lckfb-tspi-3m.dts,
usb_a_3_0 routes port@0 to usb_hub port@1 and port@1 to usb_drd1_dwc3
port@1.  When usb_drd1_dwc3's SuperSpeed port (port1 = 1) runs
find_and_link_peer(), usb_port_graph_id() returns 2, misses port@1, and
falls back to pairing with usb_drd1_dwc3's USB 2.0 root hub port.  When
usb_hub port 1 then runs match_connector_peer() with cp->port = 1,
usb_port_graph_id(peer_hdev, 1) returns 2 and fails to link the ports.

> +	if (port1 > peer_hdev->maxchild)
> +		return 0;
> +
> +	/* No connect_type check: the connector shows the port is used */
> +	peer = peer_hub->ports[port1 - 1];
> +	if (!peer)
> +		return 0;

[Severity: Medium]
When a root hub's controller node has an OF graph indexed by
usb_port_graph_id(), does usb_hub_create_port_device() pass the wrong port
number to usb_of_get_connect_type()?

drivers/usb/core/port.c:usb_hub_create_port_device() {
    ...
	port_dev->connect_type = usb_of_get_connect_type(hdev, port1);
    ...
}

For a SuperSpeed root hub port where port1 is 1 but usb_port_graph_id() is
2, usb_of_get_connect_type(hdev, 1) looks up port@1 in the controller's OF
graph instead of port@2.  When port@1 has no endpoint (as on the
SuperSpeed-only RK3588 host controller), usb_of_get_connect_type() returns
USB_PORT_NOT_USED instead of USB_PORT_CONNECT_TYPE_HOT_PLUG.  Although
match_connector_peer() skips checking connect_type,
set_usb_port_removable() later marks hotplugged USB 3.0 devices on that
connector as DEVICE_FIXED and sysfs reports the port as "not used".

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-usb-port-pairing-v3-0-d6c6edb4341c@gmail.com?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-13 17:20 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 16:39 [PATCH v3 0/2] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
2026-11-13 16:39 ` [PATCH v3 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
2026-11-13 17:20   ` sashiko-bot
2026-11-13 16:39 ` [PATCH v3 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
2026-11-13 17:08   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox