]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
util: implement support for finding host USB devices by port
authorMaximilian Martin <maximilian_martin@gmx.de>
Mon, 18 Aug 2025 14:34:13 +0000 (16:34 +0200)
committerDaniel P. Berrangé <berrange@redhat.com>
Wed, 11 Feb 2026 18:24:54 +0000 (18:24 +0000)
Extend the API for finding host USB devices, to allow requesting
a search based on the port.

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
[DB: split out of bigger patch]
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
src/hypervisor/virhostdev.c
src/util/virusb.c
src/util/virusb.h
tests/virusbtest.c

index 3717fbd47d7107101cd6c698962273fb6f2e8a43..7f1fb012be7b9d1a77571e0078feaaf1eec0cfba 100644 (file)
@@ -1373,7 +1373,7 @@ virHostdevFindUSBDeviceWithFlags(virDomainHostdevDef *hostdev,
     g_autoptr(virUSBDeviceList) devs = NULL;
     int rc;
 
-    rc = virUSBDeviceFind(vendor, product, bus, device, NULL,
+    rc = virUSBDeviceFind(vendor, product, bus, device, NULL, NULL,
                           mandatory, flags, &devs);
     if (rc < 0)
         return -1;
index 8d4ce08c0970eb30f3ba4e538e5abc8a73ab7dfe..85ad3d58ce04441fb2ddec2fc6f7491131a44efd 100644 (file)
@@ -96,11 +96,29 @@ static int virUSBSysReadFile(const char *f_name, const char *d_name,
     return 0;
 }
 
+static int
+virUSBSysReadFileStr(const char *f_name,
+                     const char *d_name,
+                     char **value)
+{
+    char *buf = NULL;
+    g_autofree char *filename = NULL;
+
+    filename = g_strdup_printf(USB_SYSFS "/devices/%s/%s", d_name, f_name);
+
+    if (virFileReadAll(filename, 1024, &buf) < 0)
+        return -1;
+
+    *value = buf;
+    return 0;
+}
+
 static virUSBDeviceList *
 virUSBDeviceSearch(unsigned int vendor,
                    unsigned int product,
                    unsigned int bus,
                    unsigned int devno,
+                   const char *port,
                    const char *vroot,
                    unsigned int flags)
 {
@@ -121,6 +139,8 @@ virUSBDeviceSearch(unsigned int vendor,
 
     while ((direrr = virDirRead(dir, &de, USB_SYSFS "/devices")) > 0) {
         unsigned int found_prod, found_vend, found_bus, found_devno;
+        g_autofree char *found_port = NULL;
+        bool port_matches;
         char *tmpstr = de->d_name;
 
         if (strchr(de->d_name, ':'))
@@ -148,6 +168,14 @@ virUSBDeviceSearch(unsigned int vendor,
                               10, &found_devno) < 0)
             goto cleanup;
 
+        if (virUSBSysReadFileStr("devpath", de->d_name,
+                                 &found_port) < 0) {
+            goto cleanup;
+        } else {
+            virStringTrimOptionalNewline(found_port);
+            port_matches = STREQ_NULLABLE(found_port, port);
+        }
+
         if (flags & USB_DEVICE_FIND_BY_VENDOR) {
             if (found_prod != product || found_vend != vendor)
                 continue;
@@ -159,6 +187,12 @@ virUSBDeviceSearch(unsigned int vendor,
             found = true;
         }
 
+        if (flags & USB_DEVICE_FIND_BY_PORT) {
+            if (found_bus != bus || !port_matches)
+                continue;
+            found = true;
+        }
+
         usb = virUSBDeviceNew(found_bus, found_devno, vroot);
 
         if (!usb)
@@ -185,6 +219,7 @@ virUSBDeviceFind(unsigned int vendor,
                  unsigned int product,
                  unsigned int bus,
                  unsigned int devno,
+                 const char *port,
                  const char *vroot,
                  bool mandatory,
                  unsigned int flags,
@@ -193,7 +228,7 @@ virUSBDeviceFind(unsigned int vendor,
     g_autoptr(virUSBDeviceList) list = NULL;
     int count;
 
-    if (!(list = virUSBDeviceSearch(vendor, product, bus, devno,
+    if (!(list = virUSBDeviceSearch(vendor, product, bus, devno, port,
                                     vroot, flags)))
         return -1;
 
@@ -206,8 +241,8 @@ virUSBDeviceFind(unsigned int vendor,
         }
 
         virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("Did not find matching USB device: vid:%1$04x, pid:%2$04x, bus:%3$u, device:%4$u"),
-                       vendor, product, bus, devno);
+                       _("Did not find matching USB device: vid:%1$04x, pid:%2$04x, bus:%3$u, device:%4$u, port:%5$s"),
+                       vendor, product, bus, devno, port ? port : "");
         return -1;
     }
 
index 73bb9c1d773eccb46f6742c7fe4742a656794749..86cc0a9d3d6f040738c892711b2c8d33ef4e6116 100644 (file)
@@ -34,6 +34,7 @@ typedef enum {
     USB_DEVICE_ALL = 0,
     USB_DEVICE_FIND_BY_VENDOR = 1 << 0,
     USB_DEVICE_FIND_BY_DEVICE = 1 << 1,
+    USB_DEVICE_FIND_BY_PORT = 1 << 2,
 } virUSBDeviceFindFlags;
 
 virUSBDevice *virUSBDeviceNew(unsigned int bus,
@@ -44,6 +45,7 @@ int virUSBDeviceFind(unsigned int vendor,
                      unsigned int product,
                      unsigned int bus,
                      unsigned int devno,
+                     const char *port,
                      const char *vroot,
                      bool mandatory,
                      unsigned int flags,
index 94e432beb887603888b25993d13c215bc51a181c..12ac338df91d3ba471bba4e1b996d1089a5b6e6a 100644 (file)
@@ -28,7 +28,9 @@
 typedef enum {
     FIND_BY_VENDOR,
     FIND_BY_DEVICE,
+    FIND_BY_PORT,
     FIND_BY_VENDOR_AND_DEVICE,
+    FIND_BY_VENDOR_AND_PORT
 } testUSBFindFlags;
 
 struct findTestInfo {
@@ -37,6 +39,7 @@ struct findTestInfo {
     unsigned int product;
     unsigned int bus;
     unsigned int devno;
+    const char *port;
     const char *vroot;
     bool mandatory;
     int how;
@@ -79,14 +82,21 @@ static int testDeviceFind(const void *opaque)
     case FIND_BY_DEVICE:
         flags = USB_DEVICE_FIND_BY_DEVICE;
         break;
+    case FIND_BY_PORT:
+        flags = USB_DEVICE_FIND_BY_PORT;
+        break;
     case FIND_BY_VENDOR_AND_DEVICE:
         flags = USB_DEVICE_FIND_BY_VENDOR |
                 USB_DEVICE_FIND_BY_DEVICE;
         break;
+    case FIND_BY_VENDOR_AND_PORT:
+        flags = USB_DEVICE_FIND_BY_VENDOR |
+                USB_DEVICE_FIND_BY_PORT;
+        break;
     }
 
     rv = virUSBDeviceFind(info->vendor, info->product,
-                          info->bus, info->devno,
+                          info->bus, info->devno, info->port,
                           info->vroot, info->mandatory, flags, &devs);
 
     if (info->expectFailure) {
@@ -112,7 +122,9 @@ static int testDeviceFind(const void *opaque)
 
     switch (info->how) {
     case FIND_BY_DEVICE:
+    case FIND_BY_PORT:
     case FIND_BY_VENDOR_AND_DEVICE:
+    case FIND_BY_VENDOR_AND_PORT:
         if (virUSBDeviceFileIterate(dev, testDeviceFileActor, NULL) < 0)
             goto cleanup;
         break;
@@ -166,7 +178,7 @@ testUSBList(const void *opaque G_GNUC_UNUSED)
         goto cleanup;
 
 #define EXPECTED_NDEVS_ONE 3
-    if (virUSBDeviceFind(0x1d6b, 0x0002, 0, 0, NULL, true,
+    if (virUSBDeviceFind(0x1d6b, 0x0002, 0, 0, NULL, NULL, true,
                          USB_DEVICE_FIND_BY_VENDOR, &devlist) < 0)
         goto cleanup;
 
@@ -190,7 +202,7 @@ testUSBList(const void *opaque G_GNUC_UNUSED)
         goto cleanup;
 
 #define EXPECTED_NDEVS_TWO 3
-    if (virUSBDeviceFind(0x18d1, 0x4e22, 0, 0, NULL, true,
+    if (virUSBDeviceFind(0x18d1, 0x4e22, 0, 0, NULL, NULL, true,
                          USB_DEVICE_FIND_BY_VENDOR, &devlist) < 0)
         goto cleanup;
 
@@ -211,7 +223,7 @@ testUSBList(const void *opaque G_GNUC_UNUSED)
                        EXPECTED_NDEVS_ONE + EXPECTED_NDEVS_TWO) < 0)
         goto cleanup;
 
-    rv = virUSBDeviceFind(0x18d1, 0x4e22, 1, 20, NULL, true,
+    rv = virUSBDeviceFind(0x18d1, 0x4e22, 1, 20, NULL, NULL, true,
                             USB_DEVICE_FIND_BY_VENDOR |
                             USB_DEVICE_FIND_BY_DEVICE, &devs);
     if (rv != 1) {
@@ -253,36 +265,50 @@ mymain(void)
     int rv = 0;
 
 #define DO_TEST_FIND_FULL(name, vend, prod, bus, devno, \
-                          vroot, mand, how, fail) \
+                          port, vroot, mand, how, fail) \
     do { \
         struct findTestInfo data = { name, vend, prod, bus, \
-            devno, vroot, mand, how, fail \
+            devno, port, vroot, mand, how, fail \
         }; \
         if (virTestRun("USBDeviceFind " name, testDeviceFind, &data) < 0) \
             rv = -1; \
     } while (0)
 
 #define DO_TEST_FIND_BY_VENDOR(name, vend, prod) \
-    DO_TEST_FIND_FULL(name, vend, prod, 123, 456, NULL, true, \
+    DO_TEST_FIND_FULL(name, vend, prod, 123, 456, NULL, NULL, true, \
                       FIND_BY_VENDOR, false)
 #define DO_TEST_FIND_BY_VENDOR_FAIL(name, vend, prod) \
-    DO_TEST_FIND_FULL(name, vend, prod, 123, 456, NULL, true, \
+    DO_TEST_FIND_FULL(name, vend, prod, 123, 456, NULL, NULL, true, \
                       FIND_BY_VENDOR, true)
 
 #define DO_TEST_FIND_BY_DEVICE(name, bus, devno) \
-    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, devno, NULL, true, \
+    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, devno, NULL, NULL, true, \
                       FIND_BY_DEVICE, false)
 #define DO_TEST_FIND_BY_DEVICE_FAIL(name, bus, devno) \
-    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, devno, NULL, true, \
+    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, devno, NULL, NULL, true, \
                       FIND_BY_DEVICE, true)
 
+#define DO_TEST_FIND_BY_PORT(name, bus, port) \
+    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, 456, port, NULL, true, \
+                      FIND_BY_PORT, false)
+#define DO_TEST_FIND_BY_PORT_FAIL(name, bus, port) \
+    DO_TEST_FIND_FULL(name, 0x1010, 0x2020, bus, 456, port, NULL, true, \
+                      FIND_BY_PORT, true)
+
 #define DO_TEST_FIND_BY_VENDOR_AND_DEVICE(name, vend, prod, bus, devno) \
-    DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, true, \
+    DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, NULL, true, \
                       FIND_BY_VENDOR_AND_DEVICE, false)
 #define DO_TEST_FIND_BY_VENDOR_AND_DEVICE_FAIL(name, vend, prod, bus, devno) \
-    DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, true, \
+    DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, NULL, true, \
                       FIND_BY_VENDOR_AND_DEVICE, true)
 
+#define DO_TEST_FIND_BY_VENDOR_AND_PORT(name, vend, prod, bus, port) \
+    DO_TEST_FIND_FULL(name, vend, prod, bus, 456, port, NULL, true, \
+                      FIND_BY_VENDOR_AND_PORT, false)
+#define DO_TEST_FIND_BY_VENDOR_AND_PORT_FAIL(name, vend, prod, bus, port) \
+    DO_TEST_FIND_FULL(name, vend, prod, bus, 456, port, NULL, true, \
+                      FIND_BY_VENDOR_AND_PORT, true)
+
     DO_TEST_FIND_BY_DEVICE("integrated camera", 1, 5);
     DO_TEST_FIND_BY_DEVICE_FAIL("wrong bus/devno combination", 2, 20);
     DO_TEST_FIND_BY_DEVICE_FAIL("missing bus", 5, 20);
@@ -292,11 +318,21 @@ mymain(void)
     DO_TEST_FIND_BY_VENDOR_FAIL("Bogus vendor and product", 0xf00d, 0xbeef);
     DO_TEST_FIND_BY_VENDOR_FAIL("Valid vendor", 0x1d6b, 0xbeef);
 
+    DO_TEST_FIND_BY_PORT("Logitech mouse", 1, "1.5.3.3");
+    DO_TEST_FIND_BY_PORT_FAIL("wrong bus/port combination", 2, "1.5.3.3");
+    DO_TEST_FIND_BY_PORT_FAIL("missing bus", 5, "1.5.3.3");
+    DO_TEST_FIND_BY_PORT_FAIL("missing port", 1, "8.2.5");
+
     DO_TEST_FIND_BY_VENDOR_AND_DEVICE("Nexus", 0x18d1, 0x4e22, 1, 20);
     DO_TEST_FIND_BY_VENDOR_AND_DEVICE_FAIL("Bogus vendor and product", 0xf00d, 0xbeef, 1, 25);
     DO_TEST_FIND_BY_VENDOR_AND_DEVICE_FAIL("Nexus wrong devnum", 0x18d1, 0x4e22, 1, 25);
     DO_TEST_FIND_BY_VENDOR_AND_DEVICE_FAIL("Bogus", 0xf00d, 0xbeef, 1024, 768);
 
+    DO_TEST_FIND_BY_VENDOR_AND_PORT("Nexus", 0x046d, 0xc069, 1, "1.5.3.3");
+    DO_TEST_FIND_BY_VENDOR_AND_PORT_FAIL("Bogus vendor and product", 0xf00d, 0xbeef, 1, "1.5.3.3");
+    DO_TEST_FIND_BY_VENDOR_AND_PORT_FAIL("Nexus wrong port", 0x18d1, 0x4e22, 1, "8.2.5");
+    DO_TEST_FIND_BY_VENDOR_AND_PORT_FAIL("Bogus", 0xf00d, 0xbeef, 1024, "1.1.1.1");
+
     if (virTestRun("USB List test", testUSBList, NULL) < 0)
         rv = -1;