From: sashiko-bot@kernel.org
To: "Arun Iyengar" <aiyengar1990@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH v4 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
Date: Sun, 15 Nov 2026 09:27:24 +0000 [thread overview]
Message-ID: <sashiko-outbox-215365@kernel.org> (raw)
In-Reply-To: <20261115-usb-port-pairing-v4-2-25d3c9adcc21@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] usb: hub: unnotified and unlocked port reset in
hub_reset_peer_child()
- [Medium] usb: hub: wrong peer disconnect on status error in
hub_reset_peer_child()
- [Medium] usb: hub: concurrent SuperSpeed enumeration before USB 2.0
peer disconnect
--
commit 86b9ce000f28b25e9610cbbbb687f6dd87200f35
Author: Arun Iyengar <aiyengar1990@gmail.com>
usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer
When a SuperSpeed root hub starts, this commit queues work to reset any
SuperSpeed-capable non-hub device attached at high speed on a USB 2.0 peer
port from a different controller, disconnecting the USB 2.0 port if the
device appears on the SuperSpeed port or restoring it with
usb_reset_device() otherwise.
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 199d3f535a891..b56ee90e55f0f 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
[ ... ]
> @@ -3278,6 +3289,127 @@ static void usb_unlock_port(struct usb_port *port_dev)
> __release(&port_dev->status_lock);
> }
>
> +static void hub_reset_peer_child(struct usb_hub *hub, int port1,
> + struct usb_device *peer_hdev, int peer_port1)
> +{
> + struct usb_hub *peer_hub;
> + struct usb_device *udev;
> +
> + usb_lock_device(peer_hdev);
> + if (hub->quiescing)
> + goto out;
> + 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;
> +
> + 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 &&
> + usb_autoresume_device(udev) == 0) {
> + struct usb_port *port_dev = peer_hub->ports[peer_port1 - 1];
> + u16 portstatus, portchange;
> + int i, ret;
> +
> + dev_info(&udev->dev,
> + "SuperSpeed peer port available, resetting\n");
> + /* A second reset hands the EHCI port to its companion */
> + usb_lock_port(port_dev);
> + ret = hub_port_reset(peer_hub, peer_port1, NULL,
> + HUB_ROOT_RESET_TIME, false);
[Severity: High]
When a SuperSpeed-capable device stays on the USB 2.0 port (for example,
when attached through a USB 2.0 cable or when SuperSpeed link training
fails), does calling hub_port_reset() directly here leave the physical
device enabled at USB address 0 for a full second without holding
address0_mutex or notifying bound interface drivers?
Unlike usb_reset_device() -> usb_reset_and_verify_device(), this reset does
not invoke driver pre_reset() callbacks, disable USB 2.0 hardware LPM, or
acquire bus_to_hcd(peer_hdev->bus)->address0_mutex, and passing NULL for
udev leaves udev->state as USB_STATE_CONFIGURED. While the loop below
sleeps for 1000 ms polling the SuperSpeed port:
- peer_port1 is enabled with the device responding to USB address 0, which
can collide with concurrent enumeration on an external hub on the same
USB 2.0 bus or a reset on another port of peer_hdev.
- Interface drivers on udev that were just woken by
usb_autoresume_device() can submit URBs to udev->devnum while the
hardware is unconfigured at address 0, failing with bus errors or timing
out in error recovery (such as usb_lock_device_for_reset() while
usb_lock_device(udev) is held for over a second).
- When usb_reset_device(udev) finally runs after the 1 s poll, any
pre_reset() callback that communicates with the device will talk to
hardware that has already been reset.
> + for (i = 0; !ret && i < 20; i++) {
> + msleep(50);
> + ret = hub->quiescing ? -ENODEV :
> + usb_hub_port_status(hub, port1, &portstatus,
> + &portchange);
> + if (!ret && (portstatus & USB_PORT_STAT_CONNECTION))
> + ret = -ENOTCONN;
> + }
> + if (ret)
> + hub_port_logical_disconnect(peer_hub, peer_port1);
[Severity: Medium]
Can an error while polling the SuperSpeed port cause
hub_reset_peer_child() to disconnect the high-speed device on peer_hub?
If hub->quiescing becomes set (for instance during system suspend or HCD
unbind) or usb_hub_port_status(hub, port1, ...) returns an error while the
device is still attached at high speed on peer_port1, ret is overwritten
with a negative errno. The if (ret) check then calls
hub_port_logical_disconnect(peer_hub, peer_port1) and skips
usb_reset_device(udev), disconnecting the working USB 2.0 device instead
of restoring it. The same happens if the SuperSpeed port already had a
connection before peer_port1 was reset.
> + /* Else hub_wq resets the port again */
> + for (i = 0; ret == -ENOTCONN && i < 20; i++) {
> + if (usb_hub_port_status(peer_hub, peer_port1, &portstatus,
> + &portchange) ||
> + !(portstatus & USB_PORT_STAT_CONNECTION))
> + break;
> + msleep(50);
> + }
> + usb_unlock_port(port_dev);
> +
> + if (!ret)
> + usb_reset_device(udev);
> + usb_autosuspend_device(udev);
> + }
> + usb_unlock_device(udev);
> +out:
> + usb_unlock_device(peer_hdev);
> +}
[Severity: Medium]
When the device does move to the SuperSpeed port, can it be enumerated and
bound on hub before the old high-speed udev is disconnected from peer_hub?
hub_reset_peer_child() holds usb_lock_device(peer_hdev) across the reset
and polling loops without locking the SuperSpeed port
hub->ports[port1 - 1], and hub_wq allows concurrent work items across ports
and hubs:
kworker/0 (peer_reset_work) kworker/1 (hub->events)
hub_reset_peer_child()
usb_lock_device(peer_hdev)
hub_port_reset(peer_hub, ...)
/* device moves to SS port */
hub_port_wait_reset()
/* sleeps up to 920 ms */
hub_event() /* on SS hub */
port_event(hub, port1)
hub_port_connect_change()
hub_port_connect()
usb_new_device(ss_udev)
/* binds SS drivers while old
* HS udev is still bound */
hub_port_logical_disconnect()
kick_hub_wq(peer_hub)
usb_unlock_device(peer_hdev)
hub_event() /* on peer_hub */
usb_lock_device(peer_hdev)
usb_disconnect(&port_dev->child)
Because hub_port_wait_reset() only exits early when
USB_PORT_STAT_CONNECTION remains set, a device that drops its USB 2.0
connection during the reset causes hub_port_wait_reset() to poll until
HUB_RESET_TIMEOUT (920 ms) while holding usb_lock_device(peer_hdev). Even
after hub_port_logical_disconnect() kicks hub_wq for peer_hub, peer_hub's
hub_event() cannot acquire usb_lock_device(peer_hdev) to run
usb_disconnect() until hub_reset_peer_child() finishes, so both the old
high-speed usb_device and the new SuperSpeed usb_device for the same
hardware can be bound to drivers at the same time. Locking
hub->ports[port1 - 1] with usb_lock_port() during hub_reset_peer_child()
would prevent port_event() on the SuperSpeed hub from enumerating the new
device until the peer reset work completes.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-usb-port-pairing-v4-0-25d3c9adcc21@gmail.com?part=2
prev parent reply other threads:[~2026-11-15 9:27 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-15 8:57 [PATCH v4 0/2] usb: core: Pair ports of different host controllers through their connector Arun Iyengar via B4 Relay
2026-11-15 8:57 ` Arun Iyengar
2026-11-15 8:57 ` [PATCH v4 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
2026-11-15 8:57 ` Arun Iyengar
2026-11-15 9:50 ` sashiko-bot
2026-11-15 8:57 ` [PATCH v4 2/2] usb: hub: Retry SuperSpeed for a device stuck on its USB 2.0 peer Arun Iyengar
2026-11-15 8:57 ` Arun Iyengar via B4 Relay
2026-11-15 9:27 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-215365@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=aiyengar1990@gmail.com \
--cc=linux-usb@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.