[Pkg-libvirt-maintainers] Bug#846173: libvirt-daemon: Fails to locate existing usb device

Ron ron at debian.org
Sun Jul 23 06:39:08 UTC 2017


Control: clone -1 -2
Control: reassign -2 qemu 1:2.7+dfsg-1
Control: retitle -2 qemu since 2.7 fails to hotplug USB devices from udev rules


Hi,

On Tue, 29 Nov 2016 08:14:03 +0100, Guido Günther wrote:
> On Mon, Nov 28, 2016 at 10:56:33PM +0000, David Gilmour wrote:
> > USB hotplug on this host was working normally as recently as a couple
> > of months ago; possibly a stretch update caused a regression. As of
> > now, I am unable to make USB hotplug to guests work.
> > 
> > Thanks so much for looking into this.
> 
> I won't have time to look into this, sorry. I suggest to:
> 
> * set libvirt debugging to debug
> * check which monitor commands get issued to attach the device
> 
> Create a new script run from the udev rule that
> 
> * checks the necessary device nodes are actually there
> * uses the above monitor commands via "virsh qemu-monitor-command"
> 
> if it still fails it's within qemu if not there's something broken in
> libvirt.

I did have some time and motivation to look into this, since we do this
for VM support in the bit-babbler (USB TRNG support) package too, and the
view that I have of it is that it's a libvirt bug, triggered by a change
in the qemu 2.7 release.  So I'm cloning a copy of it there too, since
we may want to back out that change until this is fixed properly in
libvirt.


The crux of the problem is that although QEMU gives us the option to
hotplug a USB device by vendor/product ID, or by hostbus+hostport
(the physical usb port on the host) or by hostbus+hostaddr (the logical
USB device number which changes each time it is plugged in or gets
re-enumerated) - libvirt will *always* pass it to QEMU using the
hostaddr that it determines, even if in its config you passed that as
vendor/product ID.  And libvirt gives us no option to use the hostport
at all.

This was somewhat workable (though suboptimal) in the case where you
might have multiple devices with the same product ID (a much better
solution there would be for libvirt to support vendor/product/serial
as a unique identifying tuple which never changes) - until this change
was made in QEMU 2.7:


 commit e058fa2dd599ccc780d334558be9c1d155222b80
 Author: Gerd Hoffmann <kraxel at redhat.com>
 Date:   Fri Jun 3 11:12:55 2016 +0200

    usb-host: add special case for bus+addr
    
    This patch changes usb-host behavior in case we hostbus= and hostaddr=
    properties are used to identify the usb device in question.  Instead of
    adding the device to the hotplug watchlist we try to open directly using
    the given bus number and device address.
    
    Putting a device specified by hostaddr to the hotplug watchlist isn't
    a great idea as the address isn't a fixed property.  It changes every
    time the device is plugged in.  So considering this case as "use the
    device at bus:addr _now_" is more sane.  Also usb-host will throw errors
    in case it can't initialize the host device.
    
    Note: For devices on the hotplug watchlist (hostport or vendorid or
    productid specified) qemu continues to ignore errors and keeps
    monitoring the usb bus to see if the device eventually shows up.
    
    Signed-off-by: Gerd Hoffmann <kraxel at redhat.com>
    Message-id: 1464945175-28939-1-git-send-email-kraxel at redhat.com

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 8b774f4..da59c29 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -81,6 +81,7 @@ struct USBHostDevice {
     uint32_t                         iso_urb_frames;
     uint32_t                         options;
     uint32_t                         loglevel;
+    bool                             needs_autoscan;
 
     /* state */
     QTAILQ_ENTRY(USBHostDevice)      next;
@@ -974,9 +975,32 @@ static void usb_host_exit_notifier(struct Notifier *n, void *data)
     }
 }
 
+static libusb_device *usb_host_find_ref(int bus, int addr)
+{
+    libusb_device **devs = NULL;
+    libusb_device *ret = NULL;
+    int i, n;
+
+    if (usb_host_init() != 0) {
+        return NULL;
+    }
+    n = libusb_get_device_list(ctx, &devs);
+    for (i = 0; i < n; i++) {
+        if (libusb_get_bus_number(devs[i]) == bus &&
+            libusb_get_device_address(devs[i]) == addr) {
+            ret = libusb_ref_device(devs[i]);
+            break;
+        }
+    }
+    libusb_free_device_list(devs, 1);
+    return ret;
+}
+
 static void usb_host_realize(USBDevice *udev, Error **errp)
 {
     USBHostDevice *s = USB_HOST_DEVICE(udev);
+    libusb_device *ldev;
+    int rc;
 
     if (s->match.vendor_id > 0xffff) {
         error_setg(errp, "vendorid out of range");
@@ -997,11 +1021,33 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
     QTAILQ_INIT(&s->requests);
     QTAILQ_INIT(&s->isorings);
 
+    if (s->match.addr && s->match.bus_num &&
+        !s->match.vendor_id &&
+        !s->match.product_id &&
+        !s->match.port) {
+        s->needs_autoscan = false;
+        ldev = usb_host_find_ref(s->match.bus_num,
+                                 s->match.addr);
+        if (!ldev) {
+            error_setg(errp, "failed to find host usb device %d:%d",
+                       s->match.bus_num, s->match.addr);
+            return;
+        }
+        rc = usb_host_open(s, ldev);
+        libusb_unref_device(ldev);
+        if (rc < 0) {
+            error_setg(errp, "failed to open host usb device %d:%d",
+                       s->match.bus_num, s->match.addr);
+            return;
+        }
+    } else {
+        s->needs_autoscan = true;
+        QTAILQ_INSERT_TAIL(&hostdevs, s, next);
+        usb_host_auto_check(NULL);
+    }
+
     s->exit.notify = usb_host_exit_notifier;
     qemu_add_exit_notifier(&s->exit);
-
-    QTAILQ_INSERT_TAIL(&hostdevs, s, next);
-    usb_host_auto_check(NULL);
 }
 
 static void usb_host_instance_init(Object *obj)
@@ -1019,7 +1065,9 @@ static void usb_host_handle_destroy(USBDevice *udev)
     USBHostDevice *s = USB_HOST_DEVICE(udev);
 
     qemu_remove_exit_notifier(&s->exit);
-    QTAILQ_REMOVE(&hostdevs, s, next);
+    if (s->needs_autoscan) {
+        QTAILQ_REMOVE(&hostdevs, s, next);
+    }
     usb_host_close(s);
 }
 

So what happening here is that when `virsh attach-device` is called from
a udev rule, libudev has not yet notified libusb that the device has
been plugged in, so QEMU doesn't yet see it as present, and when libvirt
passed the logic address, QEMU looks that up, and errors out because it
doesn't know about it yet.  QED.

The problem is exacerbated by Cleverness ruling out all of the easy
workarounds that I can currently see.  libvirt doesn't pass on the
vendor or product ID even if we specify it along with an <address>
in its config snippet (which would bypass the new check in QEMU and
have it added to its hotplug watchlist again).  And systemd doesn't
let us background and delay the call to virsh until after it has
delivered the event via libudev ...


Ideally what we want here is for libvirt to better support the full
functionality that QEMU provides - allowing us to actually pass the
hostport and/or vendor and product ID when desired, since there are
good reasons to want to do that aside from this bug.  And even better
if (both) allowed specifying devices by vendor/product/serial to
allow uniquely designating one (or more) of multiple similar devices
without needing external hacks to first map that to a logical device
number or physical port address.

In the meantime, it would be nice if we can relax that test in QEMU
to keep things working until a Real Fix is in.  I can certainly see
the sense in being cautious about trusting a transient logical address
there - but if adding (and removing it again) already needs to be
managed by external logic to deal with the current less-than-ideal
state of things, then it's not unreasonable for us to be able to
say to it "trust us, you can't see it yet, but we've got this under
control, just wait until you get your notification event too").


Please do keep me in the CC for any discussion of this, I am very
interested in us ultimately Getting This Right as a supported thing.
I'd planned to open some discussion on improving this a while ago
(http://bitbabbler.org/blog.html#vm_hotplug), but this bug has now
made it a more urgent thing to deal with than it previously was.

  Cheers,
  Ron



More information about the Pkg-libvirt-maintainers mailing list