Hi,

when plugging and unplugging an ure(4) adapter continuously, I came
across the following error with URE_DEBUG set:

 ure0 at uhub0 port 21 configuration 1 interface 0 "Realtek USB 10/100/1000 
LAN" rev 3.00/30.00 addr 4
 ure0: RTL8153 (0x5c10)ure_ctl: error 15
 ure_ctl: error 15
 ure_ctl: error 15

It waits for a timeout over and over again. The problem is that the
device has been unplugged, but the attach handler still issues USB
requests via ure_ctl(). While there is an early abort check with
usbd_is_dying() in that function, it does not work during attach. The
reason is that the attach handler runs from the USB exploration task.
But that task also handles device detach. So while the hardware port
change event has been noticed, it is up to the USB exploration task to
actually detach (and deactivate) the device. But it is still running
the ure(4) attach handler, which now happily waits for each USB request
to time out. And the ure(4) attach handler has a bunch of loops calling
ure_ctl(), which takes a really long time to complete altogether when
it waits for a timeout for each of them. During that time no other USB
device will get attached/detached either.

The following diff helps with debugging. Plug a ure(4) device and
unplug it within 10 seconds.

--- 8< ---
diff --git a/sys/dev/usb/if_ure.c b/sys/dev/usb/if_ure.c
index ea73db00954..93b76b421b3 100644
--- a/sys/dev/usb/if_ure.c
+++ b/sys/dev/usb/if_ure.c
@@ -1702,6 +1704,8 @@ ure_attach(struct device *parent, struct device *self, 
void *aux)
                break;
        }
 
+       usbd_delay_ms(sc->ure_udev, 10000);
+
        if (sc->ure_flags & URE_FLAG_8152)
                ure_rtl8152_init(sc);
        else if (sc->ure_flags & (URE_FLAG_8153B | URE_FLAG_8156))
--- 8< ---

The problem is that most of the callers of ure_ctl() do not check the
error return code, especially during attach. One solution could be to
propagate the error and handle it during attach. OTOH other USB device
drivers have the same problem. So one might argue that the USB stack is
at fault then. If we wanted to fix it in the USB stack, we would need
to decouple how port change events are handled. Maybe we could get away
with sneaking it into usbd_do_request(). But all of that looks rather
fragile and is well beyond me.

The easiest solution is to deactivate the device proactively in
ure_ctl() when we see that error there. Linux's rtl8152 does something
similar and checks if the device is unplugged [1][2].

[1] 
https://elixir.bootlin.com/linux/v5.14-rc5/source/drivers/net/usb/r8152.c#L1249
[2] 
https://elixir.bootlin.com/linux/v5.14-rc5/source/drivers/net/usb/r8152.c#L1293

Thanks to kevlo@ for helping me debug this.


So long,


 - Christian

--- 
 sys/dev/usb/if_ure.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletions(-)

diff --git a/sys/dev/usb/if_ure.c b/sys/dev/usb/if_ure.c
index ef2bc75a7c0..f5e0039648d 100644
--- a/sys/dev/usb/if_ure.c
+++ b/sys/dev/usb/if_ure.c
@@ -223,7 +223,7 @@ ure_ctl(struct ure_softc *sc, uint8_t rw, uint16_t val, 
uint16_t index,
        usbd_status             err;
 
        if (usbd_is_dying(sc->ure_udev))
-               return 0;
+               return -1;
 
        if (rw == URE_CTL_WRITE)
                req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
@@ -239,6 +239,8 @@ ure_ctl(struct ure_softc *sc, uint8_t rw, uint16_t val, 
uint16_t index,
        err = usbd_do_request(sc->ure_udev, &req, buf);
        if (err) {
                DPRINTF(("ure_ctl: error %d\n", err));
+               if (err == USBD_CANCELLED || err == USBD_TIMEOUT)
+                       usbd_deactivate(sc->ure_udev);
                return -1;
        }
 

Reply via email to