mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Sascha Hauer <s.hauer@pengutronix.de>
To: BAREBOX <barebox@lists.infradead.org>
Subject: [PATCH 09/13] usb: hub: detect disconnected devices
Date: Mon, 31 Aug 2026 15:20:16 +0200	[thread overview]
Message-ID: <20260831-usb-device-lifetime-v1-9-6adf4054b909@pengutronix.de> (raw)
In-Reply-To: <20260831-usb-device-lifetime-v1-0-6adf4054b909@pengutronix.de>

barebox never noticed that a USB device was unplugged. Running "usb"
again after removing a stick kept reporting it, /dev/disk0 and its
partitions stayed around and reads went to a device that was not there
anymore.

The machinery to remove a device was all there, it was just never
reached. usb_scan_port() bailed out before usb_hub_port_connect_change()
whenever the port reported no connection:

	if (!(portchange & USB_PORT_STAT_C_CONNECTION) ||
	    !(portstatus & USB_PORT_STAT_CONNECTION))

A port whose device has been unplugged has the change bit set - the hub
latches it - and the connection bit clear, which is exactly what the
second half of the condition filters out. The disconnect branch in
usb_hub_port_connect_change() could therefore never run: getting there
required the connection bit to be set, so its test for the very same bit
being clear was always false.

Let a connection change through when we have a device on that port, no
matter what the connection bit says, and decide in
usb_hub_port_connect_change() what it means: the device we knew about is
gone in either case, so remove it and enumerate a new one only if the
port reports a connection again. That also covers a device being swapped
for another one between two scans, which used to end in

	ERROR: register_device: already registered usb1-0

with the new device unusable and the old one orphaned from the port
array, so it could never be removed at all.

The previous attempt to catch that case tested for USB_PORT_STAT_ENABLE
being clear. That cannot work on xHCI: a SuperSpeed root port is enabled
by the controller during link training, so it is already enabled the
first time we look at it. Drop the test.

Ports without a device keep the old behaviour and stay in the scan list
until the connect timeout expires, so slow devices still get their
second before we give up on them.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Assisted-by: Claude:claude-opus-5
---
 drivers/usb/core/hub.c | 35 +++++++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b56c658f91..18de8badf5 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -292,20 +292,29 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port,
 	/* Clear the connection change status */
 	usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);
 
-	/* Disconnect any existing devices under this port */
-	if (dev->children[port] && !(portstatus & USB_PORT_STAT_CONNECTION)) {
+	/*
+	 * The connection changed, so whatever we knew about this port is
+	 * gone. That covers a plain unplug as well as a device that has been
+	 * replaced while we were not looking: the port reports a connection
+	 * again in that case, but not the one we have a device for.
+	 *
+	 * Note we must not test for USB_PORT_STAT_ENABLE here to tell the
+	 * two apart. An xHCI root hub enables a SuperSpeed port on its own
+	 * during link training, so the port is already enabled the first
+	 * time we see it.
+	 */
+	if (dev->children[port]) {
 		dev_dbg(&dev->dev, "port%d: disconnect detected\n", port + 1);
 		usb_remove_device(dev->children[port]);
+		dev->children[port] = NULL;
 
 		if (!dev->parent && dev->host->usbphy)
 			usb_phy_notify_disconnect(dev->host->usbphy, dev->speed);
-
-		return;
 	}
 
-	/* Remove disabled but connected devices */
-	if (dev->children[port] && !(portstatus & USB_PORT_STAT_ENABLE))
-		usb_remove_device(dev->children[port]);
+	/* Nothing connected anymore, we are done */
+	if (!(portstatus & USB_PORT_STAT_CONNECTION))
+		return;
 
 	/* Allocate a new device struct for the port */
 	usb = usb_alloc_new_device();
@@ -380,8 +389,15 @@ static void usb_scan_port(struct usb_device_scan *usb_scan)
 	dev_dbg(&dev->dev, "port%d: Status 0x%04x Change 0x%04x\n",
 			port + 1, portstatus, portchange);
 
+	/*
+	 * A connection change on a port we have a device for has to be
+	 * handled even when the port reports no connection: that is how a
+	 * disconnect looks. An empty port on the other hand keeps its change
+	 * bit set from the power-on, so leave it in the scan list until the
+	 * connect timeout expires to give slow devices time to show up.
+	 */
 	if (!(portchange & USB_PORT_STAT_C_CONNECTION) ||
-	    !(portstatus & USB_PORT_STAT_CONNECTION)) {
+	    (!(portstatus & USB_PORT_STAT_CONNECTION) && !dev->children[port])) {
 		if (get_time_ns() >= hub->connect_timeout) {
 			dev_dbg(&dev->dev, "port%d: timeout\n", port + 1);
 			/* Remove this device from scanning list */
@@ -402,8 +418,7 @@ static void usb_scan_port(struct usb_device_scan *usb_scan)
 		usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_BH_PORT_RESET);
 	}
 
-	/* A new USB device is ready at this point */
-	dev_dbg(&dev->dev, "port%d: USB dev found\n", port + 1);
+	dev_dbg(&dev->dev, "port%d: connection change\n", port + 1);
 
 	usb_hub_port_connect_change(dev, port, portstatus, portchange);
 

-- 
2.47.3




  parent reply	other threads:[~2026-08-31 13:21 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-31 13:20 [PATCH 00/13] USB: Make USB devices removable Sascha Hauer
2026-08-31 13:20 ` [PATCH 01/13] usb: don't report device removal after the device name is gone Sascha Hauer
2026-08-31 13:20 ` [PATCH 02/13] fs: devfs: count an open partition as an open device Sascha Hauer
2026-08-31 13:20 ` [PATCH 03/13] block: propagate errors from blockdevice_unregister() Sascha Hauer
2026-08-31 13:20 ` [PATCH 04/13] fs: add cdev_umount_all() Sascha Hauer
2026-08-31 13:20 ` [PATCH 05/13] block: add blockdevice_unregister_removed() Sascha Hauer
2026-08-31 13:20 ` [PATCH 06/13] usb: storage: tear the disk down properly on disconnect Sascha Hauer
2026-08-31 13:20 ` [PATCH 07/13] usb: hub: cancel pending port scans of a removed device Sascha Hauer
2026-08-31 13:20 ` [PATCH 08/13] usb: reuse the addresses of removed devices Sascha Hauer
2026-08-31 13:20 ` Sascha Hauer [this message]
2026-08-31 13:20 ` [PATCH 10/13] usb: don't keep a dangling root device on enumeration failure Sascha Hauer
2026-08-31 13:20 ` [PATCH 11/13] usb: hub: limit the number of ports to USB_MAXCHILDREN Sascha Hauer
2026-08-31 13:20 ` [PATCH 12/13] usb: detect unplugged devices on transfer errors Sascha Hauer
2026-08-31 13:20 ` [PATCH 13/13] usb: storage: stop talking to a device that is gone Sascha Hauer

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=20260831-usb-device-lifetime-v1-9-6adf4054b909@pengutronix.de \
    --to=s.hauer@pengutronix.de \
    --cc=barebox@lists.infradead.org \
    /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