Linux USB
 help / color / mirror / Atom feed
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

      reply	other threads:[~2026-11-15  9:27 UTC|newest]

Thread overview: 5+ 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 ` [PATCH v4 1/2] usb: core: Pair ports of different hubs " Arun Iyengar via B4 Relay
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 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox