* [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector
@ 2026-11-10 7:20 Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 1/4] dt-bindings: usb: generic-ehci: Allow describing the root hub ports Arun Iyengar via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Alan Stern
Cc: linux-usb, devicetree, linux-kernel, linux-arm-kernel, linux-rockchip
The USB core pairs the USB 2.0 and SuperSpeed halves of a USB 3 port
only within one xHCI (shared_hcd, same port number) or through ACPI
location data. Some SoCs route the two halves of a connector to
different host controllers: on the Rockchip RK3588, USB 3.0 host 2 is
SuperSpeed-only (its own USB 2.0 root hub port is not wired to a PHY)
and the connector's USB 2.0 lines go to a separate EHCI/OHCI pair.
Today the SuperSpeed port is paired with the dead USB 2.0 port of its
own xHCI, and the EHCI port that really shares the connector has no
peer.
That is more than a cosmetic problem on boards where VBUS of such a
port is always on. On the Orange Pi 5 the Type-A USB 3 port has no
VBUS switch at all (no enable GPIO in the vendor or mainline device
tree; confirmed on the board), so a device that is plugged in across
power-up or a reboot is powered long before the kernel runs. It looks
for a SuperSpeed link while the xHCI is still down, gives up, and
connects at high speed to the EHCI, which starts ~10 ms before the
xHCI. It then stays at 480 Mb/s until it is unplugged: USB 3 devices
only look for a SuperSpeed link again after a VBUS cycle or a USB 2.0
bus reset.
The series describes the connector in the device tree and lets the
USB core use it:
1. dt-bindings: allow a "ports" graph on generic-ehci (one port per
root hub port, as usb-hub.yaml already has for hubs).
2. usb: core: when a port's graph endpoint leads to a usb-{a,b,c}
connector whose other half ends on a different hub or host
controller, pair the two ports through the connector and keep them
out of the default pairing. Connectors whose halves end on the same
node (Type-C on a dwc3) are unchanged.
3. usb: hub: when a SuperSpeed hub with connector-paired ports starts
(or comes back from a reset / reset-resume), reset a
SuperSpeed-capable device that sits at high speed on the USB 2.0
peer, once, so that it can train its SuperSpeed link now that the
host is up. A device that still does not train stays at high
speed; there is no loop.
4. arm64: dts: describe the Orange Pi 5/5B Type-A USB 3 connector.
Other RK3588 boards with the same split would need the same node.
Honest limits: patch 3 only helps devices that retry SuperSpeed after
a USB 2.0 bus reset. A SanDisk Extreme does; a SanDisk Ultra Fit does
not and stays at high speed after the single reset. The hardware fix
(a switchable VBUS, cycled once the SuperSpeed host is up) is not
available on this board. The pairing itself (patch 2) also makes the
existing peer runtime-PM rules apply to such connectors.
Tested on an Orange Pi 5 (RK3588S): with the series the EHCI root hub
port 1 and the xHCI SuperSpeed port 1 show each other as "peer" in
sysfs. A SanDisk Extreme present at boot enumerates at high speed on
the EHCI at ~3.2 s, "SuperSpeed peer port available, resetting" is
logged once, and it re-enumerates at 5 Gb/s on the xHCI at ~3.8 s, on
every boot; without the series (two different kernels) it came up at
480 Mb/s on the EHCI on every boot and stayed there until replugged. A
SanDisk Ultra Fit that ignores the USB 2.0 reset stays at high speed
after the one reset. Hot-plug after boot (5 Gb/s), the Type-C port
(both halves on the same dwc3 node) and USB 2.0-only devices in the
port behave as before. dt_binding_check for generic-ehci and
CHECK_DTBS for the Orange Pi 5 and 5B are clean on this base.
Open point for the dwc3 binding: snps,dwc3-common.yaml describes
"ports" as used together with usb-role-switch; patch 4 uses port@1 of
a host-only dwc3 for the SuperSpeed lanes, as rk3588s-indiedroid-nova
and rk3588-lubancat-5io already do. I can send a wording update if
wanted.
This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code and the commit messages, and I reviewed and tested
every change as described above.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (4):
dt-bindings: usb: generic-ehci: Allow describing the root hub ports
usb: core: Pair ports of different hubs through their connector
usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
arm64: dts: rockchip: Describe the USB3 Type-A connector on Orange Pi 5/5B
.../devicetree/bindings/usb/generic-ehci.yaml | 13 +++
.../boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi | 60 ++++++++++++
drivers/usb/core/hub.c | 88 ++++++++++++++++++
drivers/usb/core/hub.h | 3 +
drivers/usb/core/of.c | 101 +++++++++++++++++++++
drivers/usb/core/port.c | 54 ++++++++++-
include/linux/usb/of.h | 9 ++
7 files changed, 324 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 1/4] dt-bindings: usb: generic-ehci: Allow describing the root hub ports
2026-11-10 7:20 [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
@ 2026-11-10 7:20 ` Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 2/4] usb: core: Pair ports of different hubs through their connector Arun Iyengar via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Alan Stern
Cc: linux-usb, devicetree, linux-kernel, linux-arm-kernel, linux-rockchip
From: Arun Iyengar <aiyengar1990@gmail.com>
The USB 2.0 lines and the SuperSpeed lanes of a USB 3 connector don't
always reach the same host controller. On the Rockchip RK3588, the
SuperSpeed-only USB 3.0 host 2 (xHCI) is paired on the board with one
of the separate USB 2.0 hosts (EHCI/OHCI). Only the device tree knows
which EHCI root hub port shares a connector with which SuperSpeed port.
The USB core already reads a hub's OF graph, with one graph port per
downstream port, and uses it for the port connect type. Allow the same
"ports" node on EHCI controllers, so that a root hub port can be linked
to its usb-connector node.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Documentation/devicetree/bindings/usb/generic-ehci.yaml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml
index 908b02d9175e..9dcba539d60d 100644
--- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml
+++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml
@@ -160,6 +160,19 @@ properties:
- host
- otg
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ description:
+ The root hub's downstream facing ports, linked to the connectors they
+ are wired to. Needed when a connector's SuperSpeed lanes go to a
+ different host controller, which then pairs with the root hub port
+ through the connector.
+
+ patternProperties:
+ "^port@[1-9a-f][0-9a-f]*$":
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Root hub port, numbered from 1.
+
required:
- compatible
- reg
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/4] usb: core: Pair ports of different hubs through their connector
2026-11-10 7:20 [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 1/4] dt-bindings: usb: generic-ehci: Allow describing the root hub ports Arun Iyengar via B4 Relay
@ 2026-11-10 7:20 ` Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 3/4] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 4/4] arm64: dts: rockchip: Describe the USB3 Type-A connector on Orange Pi 5/5B Arun Iyengar via B4 Relay
3 siblings, 0 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Alan Stern
Cc: linux-usb, devicetree, linux-kernel, linux-arm-kernel, linux-rockchip
From: Arun Iyengar <aiyengar1990@gmail.com>
A USB 3 port is two ports to the USB core, a USB 2.0 one and a
SuperSpeed one, and the core pairs them as peers. Root hub ports are
paired with the same port number of the other root hub of the same
xHCI, unless ACPI gives their locations.
Some SoCs route the two halves of a connector to different host
controllers. On the Rockchip RK3588, USB 3.0 host 2 is SuperSpeed-only:
its own USB 2.0 root hub port is not wired to a PHY, and the connector's
USB 2.0 lines go to a separate EHCI instead. Default pairing then joins
the SuperSpeed port to a dead USB 2.0 port, and nothing knows about the
EHCI port that really shares the connector.
A usb-connector node describes its USB 2.0 data lines as graph port 0
and its SuperSpeed lanes as graph port 1. When a hub's or host
controller's graph links one of its ports to a connector whose other
half leads to a different hub or host controller, pair the port with
that one instead, and keep it out of the default pairing. Connectors
whose two halves end on the same node, like a Type-C connector on a
DWC3, keep the default pairing.
This lets the runtime PM rules for peers (keep the SuperSpeed port
powered while its USB 2.0 peer is) apply to such connectors too.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/usb/core/hub.h | 2 +
drivers/usb/core/of.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/core/port.c | 54 ++++++++++++++++++++++++--
include/linux/usb/of.h | 9 +++++
4 files changed, 162 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 9ebc5ef54a32..78382839be48 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: @peer is on another hub, paired through a connector.
*/
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..63cead74ae8e 100644
--- a/drivers/usb/core/of.c
+++ b/drivers/usb/core/of.c
@@ -145,6 +145,107 @@ 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 device node owning the far end of @con's endpoint on @con_port,
+ * and in @port1 the number of the port that endpoint belongs to.
+ */
+static struct device_node *usb_of_connector_remote(struct device_node *con,
+ u32 con_port, u32 *port1)
+{
+ 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;
+ }
+ *port1 = 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 @port1 belongs
+ * @port1: one-based index of the port
+ * @superspeed: whether @hub is a SuperSpeed hub
+ * @peer_port1: returns the one-based index of the peer port
+ *
+ * A USB connector carries its USB 2.0 data lines on graph port 0 and its
+ * SuperSpeed lanes on graph port 1. Usually both reach the same hub or host
+ * controller, whose USB 2.0 and SuperSpeed ports are paired by default. Some
+ * SoCs instead route the two halves of a connector to different host
+ * controllers, for example a SuperSpeed-only xHCI paired with an EHCI. In that
+ * case the two ports can only be paired through the connector.
+ *
+ * Return: the device node of the hub or host controller providing the peer
+ * port, with its refcount incremented, if @port1 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,
+ int port1, bool superspeed,
+ u32 *peer_port1)
+{
+ struct device_node *np = hub->dev.of_node;
+ struct device_node *ep, *con, *remote, *peer = NULL;
+ u32 remote_port1;
+
+ if (!np || !of_graph_is_present(np))
+ return NULL;
+
+ ep = of_graph_get_endpoint_by_regs(np, port1, -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_port1);
+ if (!remote)
+ goto out;
+ of_node_put(remote);
+ if (remote != np || remote_port1 != port1)
+ goto out;
+
+ /* Peers on the same node are paired without the connector's help */
+ peer = usb_of_connector_remote(con, superspeed ? 0 : 1, peer_port1);
+ if (peer && (peer == np || !*peer_port1 ||
+ !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..1677118ae043 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -649,10 +649,39 @@ static int match_location(struct usb_device *peer_hdev, void *p)
return 0;
}
+struct usb_connector_peer {
+ struct usb_port *port_dev;
+ struct device_node *np;
+ u32 port1;
+};
+
+/*
+ * Check whether @peer_hdev is the hub that firmware describes as providing
+ * the other half of the port's connector, and link the two ports if it is.
+ */
+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;
+
+ if (!peer_hub || peer_hdev->dev.of_node != cp->np ||
+ hub_is_superspeed(peer_hdev) == cp->port_dev->is_superspeed ||
+ cp->port1 > peer_hdev->maxchild)
+ return 0;
+
+ peer = peer_hub->ports[cp->port1 - 1];
+ if (!peer || peer->connect_type == USB_PORT_NOT_USED)
+ 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 +689,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 +701,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, port1,
+ port_dev->is_superspeed, &cp.port1);
+ 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 +749,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..cfcfa26e7c69 100644
--- a/include/linux/usb/of.h
+++ b/include/linux/usb/of.h
@@ -20,6 +20,9 @@ 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,
+ int port1, bool superspeed,
+ u32 *peer_port1);
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 +52,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, int port1, bool superspeed,
+ u32 *peer_port1)
+{
+ return NULL;
+}
static inline bool usb_of_has_combined_node(struct usb_device *udev)
{
return false;
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/4] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
2026-11-10 7:20 [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 1/4] dt-bindings: usb: generic-ehci: Allow describing the root hub ports Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 2/4] usb: core: Pair ports of different hubs through their connector Arun Iyengar via B4 Relay
@ 2026-11-10 7:20 ` Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 4/4] arm64: dts: rockchip: Describe the USB3 Type-A connector on Orange Pi 5/5B Arun Iyengar via B4 Relay
3 siblings, 0 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Alan Stern
Cc: linux-usb, devicetree, linux-kernel, linux-arm-kernel, linux-rockchip
From: Arun Iyengar <aiyengar1990@gmail.com>
A SuperSpeed device looks for a SuperSpeed link first when it sees
VBUS, and connects at high speed when the link doesn't come up. It
looks again only when VBUS is cycled or after a USB 2.0 bus reset.
When a connector's two halves belong to different host controllers,
the USB 2.0 host can start first. On the Orange Pi 5 (RK3588S), whose
USB 3 Type-A port has VBUS on from power-up, a USB 3 flash drive
plugged in at boot was reset by the EHCI about 10 ms before the
SuperSpeed-only xHCI registered, so it came up at 480 Mb/s on the EHCI
in every one of 6 boots, while plugging it in after boot gave
5 Gb/s. It stays at high speed until it is unplugged.
When a SuperSpeed hub with ports paired through a connector starts, or
comes back from a reset or reset-resume, check those ports' USB 2.0
peers. If a SuperSpeed-capable device is connected at high speed there,
reset it once, so that it can connect at SuperSpeed now that the link
can train. Lock the peer hub first, so an enumeration that is under way
completes before the check. A device that still can't train its
SuperSpeed link stays connected at high speed after that one reset.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/usb/core/hub.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/core/hub.h | 1 +
2 files changed, 89 insertions(+)
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 199d3f535a89..df88de5bbf54 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1076,6 +1076,89 @@ int usb_remove_device(struct usb_device *udev)
return 0;
}
+/*
+ * A SuperSpeed port paired through a connector with a port of another host
+ * controller can start after a SuperSpeed-capable device has connected at
+ * high speed on that peer: with VBUS always on, the device looks for a
+ * SuperSpeed link while the SuperSpeed host is still down, gives up, and
+ * connects at high speed. It only tries SuperSpeed again after VBUS is cycled
+ * or after a USB 2.0 bus reset, so reset it now that the link can train.
+ */
+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;
+
+ 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;
+ 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);
+ }
+ }
+
+ hub_put(hub);
+}
+
+static void hub_queue_peer_reset(struct usb_hub *hub)
+{
+ int port1;
+
+ for (port1 = 1; port1 <= hub->hdev->maxchild; port1++) {
+ if (hub->ports[port1 - 1]->connector_peer) {
+ hub_get(hub);
+ if (!queue_work(system_power_efficient_wq,
+ &hub->peer_reset_work))
+ hub_put(hub);
+ return;
+ }
+ }
+}
+
enum hub_activation_type {
HUB_INIT, HUB_INIT2, HUB_INIT3, /* INITs must come first */
HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
@@ -1346,6 +1429,10 @@ 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 (hub_is_superspeed(hdev) && type != HUB_RESUME)
+ hub_queue_peer_reset(hub);
abort:
if (type == HUB_INIT2 || type == HUB_INIT3) {
/* Allow autosuspend if it was suppressed */
@@ -1978,6 +2065,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 78382839be48..edce19d7bf13 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 [flat|nested] 5+ messages in thread
* [PATCH 4/4] arm64: dts: rockchip: Describe the USB3 Type-A connector on Orange Pi 5/5B
2026-11-10 7:20 [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
` (2 preceding siblings ...)
2026-11-10 7:20 ` [PATCH 3/4] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
@ 2026-11-10 7:20 ` Arun Iyengar via B4 Relay
3 siblings, 0 replies; 5+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Alan Stern
Cc: linux-usb, devicetree, linux-kernel, linux-arm-kernel, linux-rockchip
From: Arun Iyengar <aiyengar1990@gmail.com>
The upper Type-A port of the Orange Pi 5 and 5B has its USB 2.0 lines
on USB 2.0 host 1 (EHCI/OHCI) and its SuperSpeed lanes on the
SuperSpeed-only USB 3.0 host 2 (xHCI). The lower Type-A port is USB 2.0
only, on host 0.
Describe the upper port as a usb-a-connector linked to both
controllers, so that the USB core pairs the EHCI root hub port with the
xHCI SuperSpeed port. Without that, a USB 3 device plugged in at boot
is enumerated by the EHCI before the xHCI is up and stays at high
speed.
VBUS of the port is not switched (the vendor DT has it on an always-on
5 V rail), so it is described as coming from vcc5v0_sys.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
.../boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi | 60 ++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
index 6e081bc06cfe..2049adc9be62 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
@@ -56,6 +56,38 @@ led {
};
};
+ /*
+ * The upper Type-A port: its USB 2.0 lines go to USB 2.0 host 1
+ * (EHCI/OHCI), its SuperSpeed lanes to the SuperSpeed-only USB 3.0
+ * host 2 (xHCI).
+ */
+ usb3_con: connector-usb3 {
+ compatible = "usb-a-connector";
+ label = "USB3";
+ vbus-supply = <&vcc5v0_sys>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ usb3_con_hs: endpoint {
+ remote-endpoint = <&usb_host1_ehci_port1>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ usb3_con_ss: endpoint {
+ remote-endpoint = <&usb_host2_xhci_ss>;
+ };
+ };
+ };
+ };
+
vbus_typec: regulator-vbus-typec {
compatible = "regulator-fixed";
enable-active-high;
@@ -229,8 +261,36 @@ usb_host0_xhci_drd_sw: endpoint {
};
};
+&usb_host1_ehci {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ reg = <1>;
+
+ usb_host1_ehci_port1: endpoint {
+ remote-endpoint = <&usb3_con_hs>;
+ };
+ };
+ };
+};
+
&usb_host2_xhci {
status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ reg = <1>;
+
+ usb_host2_xhci_ss: endpoint {
+ remote-endpoint = <&usb3_con_ss>;
+ };
+ };
+ };
};
&usbdp_phy0 {
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-10 7:20 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 7:20 [PATCH 0/4] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 1/4] dt-bindings: usb: generic-ehci: Allow describing the root hub ports Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 2/4] usb: core: Pair ports of different hubs through their connector Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 3/4] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar via B4 Relay
2026-11-10 7:20 ` [PATCH 4/4] arm64: dts: rockchip: Describe the USB3 Type-A connector on Orange Pi 5/5B Arun Iyengar via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®