[PATCH 0/4] USB hostdev: allow addressing by port

This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník. Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration. This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute. This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H... Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513 Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath -- 2.39.5

From: Maximilian Martin <maximilian_martin@gmx.de> This patch adds devpath files to the virusb test data. These files are mockups for the USB sysfs files that contain the port of a USB device in dotted notation. They are used for testing of USB bus/port matching. Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath | 1 + tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath | 1 + 15 files changed, 15 insertions(+) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath new file mode 100644 index 0000000000..02a7fbef02 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath @@ -0,0 +1 @@ +1.5.3.1 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath new file mode 100644 index 0000000000..23ca863cd4 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath @@ -0,0 +1 @@ +1.5.3.3 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath new file mode 100644 index 0000000000..8af85beb51 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath @@ -0,0 +1 @@ +1.5.3 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath new file mode 100644 index 0000000000..94fe62c274 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath @@ -0,0 +1 @@ +1.5.4 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath new file mode 100644 index 0000000000..9075be4951 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath @@ -0,0 +1 @@ +1.5.5 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath new file mode 100644 index 0000000000..eac1e0ada6 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath @@ -0,0 +1 @@ +1.5.6 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath new file mode 100644 index 0000000000..c239c60cba --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath @@ -0,0 +1 @@ +1.5 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath new file mode 100644 index 0000000000..810ee4e91e --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath @@ -0,0 +1 @@ +1.6 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath b/tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath @@ -0,0 +1 @@ +1 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath b/tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath new file mode 100644 index 0000000000..5625e59da8 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath @@ -0,0 +1 @@ +1.2 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath b/tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath @@ -0,0 +1 @@ +1 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath b/tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath @@ -0,0 +1 @@ +0 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath b/tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath @@ -0,0 +1 @@ +0 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath b/tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath @@ -0,0 +1 @@ +0 diff --git a/tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath b/tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath @@ -0,0 +1 @@ +0 -- 2.39.5

From: Maximilian Martin <maximilian_martin@gmx.de> This patch implements USB bus/port matching. In addition to vendor/product and bus/device matching, bus/port matching is implemented. This involves additions and refactorings in the domain configuration, host device handling, and USB helpers. Also, test methods are refactored and extended. Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- src/conf/domain_conf.c | 69 ++++++++++++++-- src/conf/domain_conf.h | 1 + src/hypervisor/virhostdev.c | 131 +++++++++++++++++------------- src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++++++++------------------------ src/util/virusb.h | 32 ++++---- tests/virusbtest.c | 149 ++++++++++++++++++++++++---------- 7 files changed, 312 insertions(+), 228 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 542d6ade91..423cad99e5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2670,6 +2670,15 @@ virDomainHostdevSubsysSCSIClear(virDomainHostdevSubsysSCSI *scsisrc) } } +static void +virDomainHostdevSubsysUSBClear(virDomainHostdevSubsysUSB *usbsrc) +{ + if (!usbsrc) + return; + + VIR_FREE(usbsrc->port); +} + static void virDomainHostdevDefClear(virDomainHostdevDef *def) @@ -2713,6 +2722,8 @@ virDomainHostdevDefClear(virDomainHostdevDef *def) g_clear_pointer(&def->source.subsys.u.pci.origstates, virBitmapFree); break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: + virDomainHostdevSubsysUSBClear(&def->source.subsys.u.usb); + break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST: break; @@ -5945,13 +5956,47 @@ virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node, } if ((addressNode = virXPathNode("./address", ctxt))) { - if (virXMLPropUInt(addressNode, "bus", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0) + bool found_device = false; + bool found_port = false; + char *port = NULL; + int ret = -1; + + ret = virXMLPropUInt(addressNode, "bus", 0, + VIR_XML_PROP_REQUIRED, &usbsrc->bus); + if (ret < 0) { + return -1; + } else if (ret == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing bus")); return -1; + } - if (virXMLPropUInt(addressNode, "device", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->device) < 0) + ret = virXMLPropUInt(addressNode, "device", 0, + VIR_XML_PROP_NONE, &usbsrc->device); + if (ret < 0) return -1; + else if (ret > 0) + found_device = true; + + port = virXMLPropString(addressNode, "port"); + if (port) { + if (*port) { + usbsrc->port = port; + found_port = true; + } else { + VIR_FREE(port); + } + } + + if (!found_device && !found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("usb address needs either device id or port")); + return -1; + } else if (found_device && found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("found both device id and port in usb address (ambiguous setting)")); + return -1; + } } return 0; @@ -14774,8 +14819,13 @@ virDomainHostdevMatchSubsysUSB(virDomainHostdevDef *first, virDomainHostdevSubsysUSB *first_usbsrc = &first->source.subsys.u.usb; virDomainHostdevSubsysUSB *second_usbsrc = &second->source.subsys.u.usb; - if (first_usbsrc->bus && first_usbsrc->device) { - /* specified by bus location on host */ + if (first_usbsrc->bus && first_usbsrc->port) { + /* specified by bus and port on host */ + if (first_usbsrc->bus == second_usbsrc->bus && + STREQ_NULLABLE(first_usbsrc->port, second_usbsrc->port)) + return 1; + } else if (first_usbsrc->bus && first_usbsrc->device) { + /* specified by bus and device id on host */ if (first_usbsrc->bus == second_usbsrc->bus && first_usbsrc->device == second_usbsrc->device) return 1; @@ -24345,10 +24395,15 @@ virDomainHostdevDefFormatSubsysUSB(virBuffer *buf, virBufferAsprintf(&sourceChildBuf, "<product id='0x%.4x'/>\n", usbsrc->product); } - if (usbsrc->bus || usbsrc->device) + if (usbsrc->bus && usbsrc->port) { + virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' port='%s'/>\n", + includeTypeInAddr ? "type='usb' " : "", + usbsrc->bus, usbsrc->port); + } else if (usbsrc->bus || usbsrc->device) { virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' device='%d'/>\n", includeTypeInAddr ? "type='usb' " : "", usbsrc->bus, usbsrc->device); + } virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 58b97a2b54..aaa8b8573a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -228,6 +228,7 @@ struct _virDomainHostdevSubsysUSB { on vendor/product */ unsigned bus; unsigned device; + char *port; unsigned vendor; unsigned product; diff --git a/src/hypervisor/virhostdev.c b/src/hypervisor/virhostdev.c index 0a1d8500d4..9e77477a9b 100644 --- a/src/hypervisor/virhostdev.c +++ b/src/hypervisor/virhostdev.c @@ -1358,6 +1358,42 @@ virHostdevMarkUSBDevices(virHostdevManager *mgr, return -1; } +static int +virHostdevFindUSBDeviceWithFlags(virDomainHostdevDef *hostdev, + bool mandatory, + unsigned int flags, + virUSBDevice **usb) +{ + virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; + unsigned vendor = usbsrc->vendor; + unsigned product = usbsrc->product; + unsigned bus = usbsrc->bus; + char *port = usbsrc->port; + unsigned device = usbsrc->device; + virUSBDeviceList *devs; + int rc; + + rc = virUSBDeviceFind(vendor, product, bus, device, port, NULL, + mandatory, flags, &devs); + if (rc < 0) + return -1; + + if (rc == 1) { + *usb = virUSBDeviceListGet(devs, 0); + virUSBDeviceListSteal(devs, *usb); + } + virObjectUnref(devs); + + if (rc > 1) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Multiple USB devices for %1$x:%2$x, use <address> to specify one"), + vendor, product); + return -1; + } + + return rc; +} + int virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, @@ -1366,77 +1402,64 @@ virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, { virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; unsigned vendor = usbsrc->vendor; - unsigned product = usbsrc->product; unsigned bus = usbsrc->bus; unsigned device = usbsrc->device; + char *port = usbsrc->port; bool autoAddress = usbsrc->autoAddress; + unsigned int flags = 0; int rc; *usb = NULL; - if (vendor && bus) { - rc = virUSBDeviceFind(vendor, product, bus, device, - NULL, - autoAddress ? false : mandatory, - usb); - if (rc < 0) { - return -1; - } else if (!autoAddress) { - goto out; - } else { - VIR_INFO("USB device %x:%x could not be found at previous" - " address (bus:%u device:%u)", - vendor, product, bus, device); - } + if (vendor) + flags |= USB_DEVICE_FIND_BY_VENDOR; + if (device) + flags |= USB_DEVICE_FIND_BY_DEVICE; + if (port) + flags |= USB_DEVICE_FIND_BY_PORT; + + /* Rule out invalid cases. */ + if (vendor && device && port) { + VIR_WARN("Cannot match USB device on vendor/product, bus/device, and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + } else if (device && port) { + VIR_WARN("Cannot match USB device on bus/device and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + } else if (!vendor && !device && !port) { + VIR_WARN("No matching fields for USB device found. Vendor/product, bus/device, or bus/port required."); + return -1; } - /* When vendor is specified, its USB address is either unspecified or the - * device could not be found at the USB device where it had been - * automatically found before. - */ - if (vendor) { - g_autoptr(virUSBDeviceList) devs = NULL; + /* First attempt, matching on all valid fields. */ + rc = virHostdevFindUSBDeviceWithFlags(hostdev, + autoAddress ? false : mandatory, + flags, usb); + if (rc < 0) + return -1; - rc = virUSBDeviceFindByVendor(vendor, product, NULL, mandatory, &devs); - if (rc < 0) { - return -1; - } else if (rc == 0) { - goto out; - } else if (rc > 1) { - if (autoAddress) { - virReportError(VIR_ERR_OPERATION_FAILED, - _("Multiple USB devices for %1$x:%2$x were found, but none of them is at bus:%3$u device:%4$u"), - vendor, product, bus, device); - } else { - virReportError(VIR_ERR_OPERATION_FAILED, - _("Multiple USB devices for %1$x:%2$x, use <address> to specify one"), - vendor, product); - } + if (rc != 1 && autoAddress && device) { + VIR_INFO("USB device could not be found at previous address " + "(bus:%u device:%u)", bus, device); + + /* Second attempt, for when the device number has changed. */ + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + usbsrc->device = 0; + + rc = virHostdevFindUSBDeviceWithFlags(hostdev, mandatory, + flags, usb); + if (rc < 0) return -1; - } - *usb = virUSBDeviceListGet(devs, 0); - virUSBDeviceListSteal(devs, *usb); + usbsrc->autoAddress = true; + } + if (!*usb) { + hostdev->missing = true; + } else if (!usbsrc->bus || !usbsrc->device) { usbsrc->bus = virUSBDeviceGetBus(*usb); usbsrc->device = virUSBDeviceGetDevno(*usb); - usbsrc->autoAddress = true; - - if (autoAddress) { - VIR_INFO("USB device %x:%x found at bus:%u device:%u (moved" - " from bus:%u device:%u)", - vendor, product, - usbsrc->bus, usbsrc->device, - bus, device); - } - } else if (bus) { - if (virUSBDeviceFindByBus(bus, device, NULL, mandatory, usb) < 0) - return -1; } - out: - if (!*usb) - hostdev->missing = true; return 0; } diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a8ebf9efd8..cd7d00674f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3679,8 +3679,6 @@ virURIResolveAlias; # util/virusb.h virUSBDeviceFileIterate; virUSBDeviceFind; -virUSBDeviceFindByBus; -virUSBDeviceFindByVendor; virUSBDeviceFree; virUSBDeviceGetBus; virUSBDeviceGetDevno; diff --git a/src/util/virusb.c b/src/util/virusb.c index cf3461f80a..8aca190c4c 100644 --- a/src/util/virusb.c +++ b/src/util/virusb.c @@ -61,12 +61,6 @@ struct _virUSBDeviceList { virUSBDevice **devs; }; -typedef enum { - USB_DEVICE_ALL = 0, - USB_DEVICE_FIND_BY_VENDOR = 1 << 0, - USB_DEVICE_FIND_BY_BUS = 1 << 1, -} virUSBDeviceFindFlags; - static virClass *virUSBDeviceListClass; static void virUSBDeviceListDispose(void *obj); @@ -102,11 +96,27 @@ 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) { @@ -127,6 +137,8 @@ virUSBDeviceSearch(unsigned int vendor, while ((direrr = virDirRead(dir, &de, USB_SYSFS "/devices")) > 0) { unsigned int found_prod, found_vend, found_bus, found_devno; + char *found_port; + bool port_matches; char *tmpstr = de->d_name; if (strchr(de->d_name, ':')) @@ -154,16 +166,32 @@ virUSBDeviceSearch(unsigned int vendor, 10, &found_devno) < 0) goto cleanup; - if ((flags & USB_DEVICE_FIND_BY_VENDOR) && - (found_prod != product || found_vend != vendor)) - continue; + if (virUSBSysReadFileStr("devpath", de->d_name, + &found_port) < 0) { + goto cleanup; + } else { + found_port[strlen(found_port) - 1] = '\0'; /* remove newline */ + port_matches = STREQ_NULLABLE(found_port, port); + VIR_FREE(found_port); + } - if (flags & USB_DEVICE_FIND_BY_BUS) { + if (flags & USB_DEVICE_FIND_BY_VENDOR) { + if (found_prod != product || found_vend != vendor) + continue; + } + + if (flags & USB_DEVICE_FIND_BY_DEVICE) { if (found_bus != bus || found_devno != devno) continue; 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) @@ -186,36 +214,38 @@ virUSBDeviceSearch(unsigned int vendor, } int -virUSBDeviceFindByVendor(unsigned int vendor, - unsigned int product, - const char *vroot, - bool mandatory, - virUSBDeviceList **devices) +virUSBDeviceFind(unsigned int vendor, + unsigned int product, + unsigned int bus, + unsigned int devno, + const char *port, + const char *vroot, + bool mandatory, + unsigned int flags, + virUSBDeviceList **devices) { virUSBDeviceList *list; int count; - if (!(list = virUSBDeviceSearch(vendor, product, 0, 0, - vroot, - USB_DEVICE_FIND_BY_VENDOR))) + if (!(list = virUSBDeviceSearch(vendor, product, bus, devno, port, + vroot, flags))) return -1; - if (list->count == 0) { + count = list->count; + if (count == 0) { virObjectUnref(list); if (!mandatory) { - VIR_DEBUG("Did not find USB device %04x:%04x", - vendor, product); if (devices) *devices = NULL; return 0; } virReportError(VIR_ERR_INTERNAL_ERROR, - _("Did not find USB device %1$04x:%2$04x"), vendor, product); + _("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; } - count = list->count; if (devices) *devices = list; else @@ -224,86 +254,6 @@ virUSBDeviceFindByVendor(unsigned int vendor, return count; } -int -virUSBDeviceFindByBus(unsigned int bus, - unsigned int devno, - const char *vroot, - bool mandatory, - virUSBDevice **usb) -{ - virUSBDeviceList *list; - - if (!(list = virUSBDeviceSearch(0, 0, bus, devno, - vroot, - USB_DEVICE_FIND_BY_BUS))) - return -1; - - if (list->count == 0) { - virObjectUnref(list); - if (!mandatory) { - VIR_DEBUG("Did not find USB device bus:%u device:%u", - bus, devno); - if (usb) - *usb = NULL; - return 0; - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Did not find USB device bus:%1$u device:%2$u"), - bus, devno); - return -1; - } - - if (usb) { - *usb = virUSBDeviceListGet(list, 0); - virUSBDeviceListSteal(list, *usb); - } - virObjectUnref(list); - - return 0; -} - -int -virUSBDeviceFind(unsigned int vendor, - unsigned int product, - unsigned int bus, - unsigned int devno, - const char *vroot, - bool mandatory, - virUSBDevice **usb) -{ - virUSBDeviceList *list; - - unsigned int flags = USB_DEVICE_FIND_BY_VENDOR|USB_DEVICE_FIND_BY_BUS; - if (!(list = virUSBDeviceSearch(vendor, product, bus, devno, - vroot, flags))) - return -1; - - if (list->count == 0) { - virObjectUnref(list); - if (!mandatory) { - VIR_DEBUG("Did not find USB device %04x:%04x bus:%u device:%u", - vendor, product, bus, devno); - if (usb) - *usb = NULL; - return 0; - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Did not find USB device %1$04x:%2$04x bus:%3$u device:%4$u"), - vendor, product, bus, devno); - return -1; - } - - if (usb) { - *usb = virUSBDeviceListGet(list, 0); - virUSBDeviceListSteal(list, *usb); - } - virObjectUnref(list); - - return 0; -} - virUSBDevice * virUSBDeviceNew(unsigned int bus, unsigned int devno, diff --git a/src/util/virusb.h b/src/util/virusb.h index d2b3f69942..cfd1ad51cc 100644 --- a/src/util/virusb.h +++ b/src/util/virusb.h @@ -30,30 +30,26 @@ typedef struct _virUSBDeviceList virUSBDeviceList; G_DEFINE_AUTOPTR_CLEANUP_FUNC(virUSBDeviceList, virObjectUnref); +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, unsigned int devno, const char *vroot); -int virUSBDeviceFindByBus(unsigned int bus, - unsigned int devno, - const char *vroot, - bool mandatory, - virUSBDevice **usb); - -int virUSBDeviceFindByVendor(unsigned int vendor, - unsigned int product, - const char *vroot, - bool mandatory, - virUSBDeviceList **devices); - int virUSBDeviceFind(unsigned int vendor, - unsigned int product, - unsigned int bus, - unsigned int devno, - const char *vroot, - bool mandatory, - virUSBDevice **usb); + unsigned int product, + unsigned int bus, + unsigned int devno, + const char *port, + const char *vroot, + bool mandatory, + unsigned int flags, + virUSBDeviceList **devices); void virUSBDeviceFree(virUSBDevice *dev); int virUSBDeviceSetUsedBy(virUSBDevice *dev, diff --git a/tests/virusbtest.c b/tests/virusbtest.c index 870e136321..62b7b8bc29 100644 --- a/tests/virusbtest.c +++ b/tests/virusbtest.c @@ -26,9 +26,11 @@ #define VIR_FROM_THIS VIR_FROM_NONE typedef enum { - FIND_BY_ALL, FIND_BY_VENDOR, - FIND_BY_BUS + 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; @@ -70,25 +73,34 @@ static int testDeviceFind(const void *opaque) g_autoptr(virUSBDeviceList) devs = NULL; int rv = 0; size_t i, ndevs = 0; + unsigned int flags = 0; switch (info->how) { - case FIND_BY_ALL: - rv = virUSBDeviceFind(info->vendor, info->product, - info->bus, info->devno, - info->vroot, info->mandatory, &dev); - break; case FIND_BY_VENDOR: - rv = virUSBDeviceFindByVendor(info->vendor, info->product, - info->vroot, info->mandatory, &devs); + flags = USB_DEVICE_FIND_BY_VENDOR; + break; + 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_BUS: - rv = virUSBDeviceFindByBus(info->bus, info->devno, - info->vroot, info->mandatory, &dev); + 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->port, + info->vroot, info->mandatory, flags, &devs); + if (info->expectFailure) { - if (rv == 0) { + if (rv >= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "unexpected success"); } else { @@ -99,9 +111,20 @@ static int testDeviceFind(const void *opaque) goto cleanup; } + if (info->how != FIND_BY_VENDOR) { + if (rv == 1) { + dev = virUSBDeviceListGet(devs, 0); + virUSBDeviceListSteal(devs, dev); + } else { + goto cleanup; + } + } + switch (info->how) { - case FIND_BY_ALL: - case FIND_BY_BUS: + 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; @@ -146,14 +169,17 @@ testUSBList(const void *opaque G_GNUC_UNUSED) virUSBDeviceList *list = NULL; virUSBDeviceList *devlist = NULL; virUSBDevice *dev = NULL; + virUSBDeviceList *devs = NULL; int ret = -1; + int rv; size_t i, ndevs; if (!(list = virUSBDeviceListNew())) goto cleanup; #define EXPECTED_NDEVS_ONE 3 - if (virUSBDeviceFindByVendor(0x1d6b, 0x0002, NULL, true, &devlist) < 0) + if (virUSBDeviceFind(0x1d6b, 0x0002, 0, 0, NULL, NULL, true, + USB_DEVICE_FIND_BY_VENDOR, &devlist) < 0) goto cleanup; ndevs = virUSBDeviceListCount(devlist); @@ -176,7 +202,8 @@ testUSBList(const void *opaque G_GNUC_UNUSED) goto cleanup; #define EXPECTED_NDEVS_TWO 3 - if (virUSBDeviceFindByVendor(0x18d1, 0x4e22, NULL, true, &devlist) < 0) + if (virUSBDeviceFind(0x18d1, 0x4e22, 0, 0, NULL, NULL, true, + USB_DEVICE_FIND_BY_VENDOR, &devlist) < 0) goto cleanup; ndevs = virUSBDeviceListCount(devlist); @@ -196,8 +223,16 @@ testUSBList(const void *opaque G_GNUC_UNUSED) EXPECTED_NDEVS_ONE + EXPECTED_NDEVS_TWO) < 0) goto cleanup; - if (virUSBDeviceFind(0x18d1, 0x4e22, 1, 20, NULL, true, &dev) < 0) + rv = virUSBDeviceFind(0x18d1, 0x4e22, 1, 20, NULL, NULL, true, + USB_DEVICE_FIND_BY_VENDOR | + USB_DEVICE_FIND_BY_DEVICE, &devs); + if (rv != 1) { goto cleanup; + } else { + dev = virUSBDeviceListGet(devs, 0); + virUSBDeviceListSteal(devs, dev); + } + virObjectUnref(devs); if (!virUSBDeviceListFind(list, dev)) { virReportError(VIR_ERR_INTERNAL_ERROR, @@ -229,49 +264,75 @@ mymain(void) { int rv = 0; -#define DO_TEST_FIND_FULL(name, vend, prod, bus, devno, vroot, mand, how, fail) \ +#define DO_TEST_FIND_FULL(name, vend, prod, bus, devno, \ + 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(name, vend, prod, bus, devno) \ - DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, true, \ - FIND_BY_ALL, false) -#define DO_TEST_FIND_FAIL(name, vend, prod, bus, devno) \ - DO_TEST_FIND_FULL(name, vend, prod, bus, devno, NULL, true, \ - FIND_BY_ALL, true) - -#define DO_TEST_FIND_BY_BUS(name, bus, devno) \ - DO_TEST_FIND_FULL(name, 101, 202, bus, devno, NULL, true, \ - FIND_BY_BUS, false) -#define DO_TEST_FIND_BY_BUS_FAIL(name, bus, devno) \ - DO_TEST_FIND_FULL(name, 101, 202, bus, devno, NULL, true, \ - FIND_BY_BUS, true) - #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) - DO_TEST_FIND("Nexus", 0x18d1, 0x4e22, 1, 20); - DO_TEST_FIND_FAIL("Nexus wrong devnum", 0x18d1, 0x4e22, 1, 25); - DO_TEST_FIND_FAIL("Bogus", 0xf00d, 0xbeef, 1024, 768); - - DO_TEST_FIND_BY_BUS("integrated camera", 1, 5); - DO_TEST_FIND_BY_BUS_FAIL("wrong bus/devno combination", 2, 20); - DO_TEST_FIND_BY_BUS_FAIL("missing bus", 5, 20); - DO_TEST_FIND_BY_BUS_FAIL("missing devnum", 1, 158); +#define DO_TEST_FIND_BY_DEVICE(name, bus, devno) \ + 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, 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, 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, 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); + DO_TEST_FIND_BY_DEVICE_FAIL("missing devnum", 1, 158); DO_TEST_FIND_BY_VENDOR("Nexus (multiple results)", 0x18d1, 0x4e22); 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; -- 2.39.5

On 4/21/25 21:38, Maximilian Martin via Devel wrote:
From: Maximilian Martin <maximilian_martin@gmx.de>
This patch implements USB bus/port matching. In addition to vendor/product and bus/device matching, bus/port matching is implemented. This involves additions and refactorings in the domain configuration, host device handling, and USB helpers. Also, test methods are refactored and extended.
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- src/conf/domain_conf.c | 69 ++++++++++++++-- src/conf/domain_conf.h | 1 + src/hypervisor/virhostdev.c | 131 +++++++++++++++++------------- src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++++++++------------------------ src/util/virusb.h | 32 ++++---- tests/virusbtest.c | 149 ++++++++++++++++++++++++---------- 7 files changed, 312 insertions(+), 228 deletions(-)
There's still a lot happening here, but see my comments below.
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 542d6ade91..423cad99e5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2670,6 +2670,15 @@ virDomainHostdevSubsysSCSIClear(virDomainHostdevSubsysSCSI *scsisrc) } }
+static void +virDomainHostdevSubsysUSBClear(virDomainHostdevSubsysUSB *usbsrc) +{ + if (!usbsrc) + return; + + VIR_FREE(usbsrc->port); +} +
static void virDomainHostdevDefClear(virDomainHostdevDef *def) @@ -2713,6 +2722,8 @@ virDomainHostdevDefClear(virDomainHostdevDef *def) g_clear_pointer(&def->source.subsys.u.pci.origstates, virBitmapFree); break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: + virDomainHostdevSubsysUSBClear(&def->source.subsys.u.usb); + break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST: break; @@ -5945,13 +5956,47 @@ virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node, }
if ((addressNode = virXPathNode("./address", ctxt))) { - if (virXMLPropUInt(addressNode, "bus", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0) + bool found_device = false; + bool found_port = false;
Nitpick, we prefer cammelCase.
+ char *port = NULL; + int ret = -1;
The 'ret' name is reserved for actual return value of functions. For storing of retvals of other functions you can use 'rc', 'rv' or anything else.
+ + ret = virXMLPropUInt(addressNode, "bus", 0, + VIR_XML_PROP_REQUIRED, &usbsrc->bus); + if (ret < 0) { + return -1; + } else if (ret == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing bus")); return -1;
This case can never happen. When VIR_XML_PROP_REQUIRED flag is specified then virXMLProp*() functions return either -1 or 1.
+ }
- if (virXMLPropUInt(addressNode, "device", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->device) < 0) + ret = virXMLPropUInt(addressNode, "device", 0, + VIR_XML_PROP_NONE, &usbsrc->device); + if (ret < 0) return -1; + else if (ret > 0) + found_device = true; + + port = virXMLPropString(addressNode, "port"); + if (port) { + if (*port) { + usbsrc->port = port; + found_port = true; + } else { + VIR_FREE(port);
If 'port' is declared as g_autofree then this is needless.
+ } + } + + if (!found_device && !found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("usb address needs either device id or port")); + return -1; + } else if (found_device && found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("found both device id and port in usb address (ambiguous setting)")); + return -1; + } }
return 0; @@ -14774,8 +14819,13 @@ virDomainHostdevMatchSubsysUSB(virDomainHostdevDef *first, virDomainHostdevSubsysUSB *first_usbsrc = &first->source.subsys.u.usb; virDomainHostdevSubsysUSB *second_usbsrc = &second->source.subsys.u.usb;
- if (first_usbsrc->bus && first_usbsrc->device) { - /* specified by bus location on host */ + if (first_usbsrc->bus && first_usbsrc->port) { + /* specified by bus and port on host */ + if (first_usbsrc->bus == second_usbsrc->bus && + STREQ_NULLABLE(first_usbsrc->port, second_usbsrc->port)) + return 1; + } else if (first_usbsrc->bus && first_usbsrc->device) { + /* specified by bus and device id on host */ if (first_usbsrc->bus == second_usbsrc->bus && first_usbsrc->device == second_usbsrc->device) return 1; @@ -24345,10 +24395,15 @@ virDomainHostdevDefFormatSubsysUSB(virBuffer *buf, virBufferAsprintf(&sourceChildBuf, "<product id='0x%.4x'/>\n", usbsrc->product); }
- if (usbsrc->bus || usbsrc->device) + if (usbsrc->bus && usbsrc->port) { + virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' port='%s'/>\n", + includeTypeInAddr ? "type='usb' " : "", + usbsrc->bus, usbsrc->port); + } else if (usbsrc->bus || usbsrc->device) { virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' device='%d'/>\n", includeTypeInAddr ? "type='usb' " : "", usbsrc->bus, usbsrc->device); + }
virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 58b97a2b54..aaa8b8573a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -228,6 +228,7 @@ struct _virDomainHostdevSubsysUSB { on vendor/product */ unsigned bus; unsigned device; + char *port;
unsigned vendor; unsigned product; diff --git a/src/hypervisor/virhostdev.c b/src/hypervisor/virhostdev.c index 0a1d8500d4..9e77477a9b 100644 --- a/src/hypervisor/virhostdev.c +++ b/src/hypervisor/virhostdev.c @@ -1358,6 +1358,42 @@ virHostdevMarkUSBDevices(virHostdevManager *mgr, return -1; }
+static int +virHostdevFindUSBDeviceWithFlags(virDomainHostdevDef *hostdev, + bool mandatory, + unsigned int flags, + virUSBDevice **usb) +{ + virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; + unsigned vendor = usbsrc->vendor; + unsigned product = usbsrc->product; + unsigned bus = usbsrc->bus; + char *port = usbsrc->port; + unsigned device = usbsrc->device; + virUSBDeviceList *devs;
g_autoptr(virUSBDeviceList) devs = NULL;
+ int rc; + + rc = virUSBDeviceFind(vendor, product, bus, device, port, NULL, + mandatory, flags, &devs); + if (rc < 0) + return -1; + + if (rc == 1) { + *usb = virUSBDeviceListGet(devs, 0); + virUSBDeviceListSteal(devs, *usb); + } + virObjectUnref(devs); + + if (rc > 1) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Multiple USB devices for %1$x:%2$x, use <address> to specify one"), + vendor, product); + return -1; + } + + return rc; +} +
int virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, @@ -1366,77 +1402,64 @@ virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, { virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; unsigned vendor = usbsrc->vendor; - unsigned product = usbsrc->product; unsigned bus = usbsrc->bus; unsigned device = usbsrc->device; + char *port = usbsrc->port; bool autoAddress = usbsrc->autoAddress; + unsigned int flags = 0; int rc;
*usb = NULL;
- if (vendor && bus) { - rc = virUSBDeviceFind(vendor, product, bus, device, - NULL, - autoAddress ? false : mandatory, - usb); - if (rc < 0) { - return -1; - } else if (!autoAddress) { - goto out; - } else { - VIR_INFO("USB device %x:%x could not be found at previous" - " address (bus:%u device:%u)", - vendor, product, bus, device); - } + if (vendor) + flags |= USB_DEVICE_FIND_BY_VENDOR; + if (device) + flags |= USB_DEVICE_FIND_BY_DEVICE; + if (port) + flags |= USB_DEVICE_FIND_BY_PORT; + + /* Rule out invalid cases. */ + if (vendor && device && port) { + VIR_WARN("Cannot match USB device on vendor/product, bus/device, and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE);
This typecasting is unnecessary. These values are already declared as non-negative values. And in C99 enums must fit into int. Everywhere else in our code we clear flags without typecasting.
+ } else if (device && port) { + VIR_WARN("Cannot match USB device on bus/device and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + } else if (!vendor && !device && !port) { + VIR_WARN("No matching fields for USB device found. Vendor/product, bus/device, or bus/port required."); + return -1;
Since this returns an error the VIR_WARN() should be promoted to virReportError().
}
- /* When vendor is specified, its USB address is either unspecified or the - * device could not be found at the USB device where it had been - * automatically found before. - */ - if (vendor) { - g_autoptr(virUSBDeviceList) devs = NULL; + /* First attempt, matching on all valid fields. */ + rc = virHostdevFindUSBDeviceWithFlags(hostdev, + autoAddress ? false : mandatory, + flags, usb); + if (rc < 0) + return -1;
- rc = virUSBDeviceFindByVendor(vendor, product, NULL, mandatory, &devs); - if (rc < 0) { - return -1; - } else if (rc == 0) { - goto out; - } else if (rc > 1) { - if (autoAddress) { - virReportError(VIR_ERR_OPERATION_FAILED, - _("Multiple USB devices for %1$x:%2$x were found, but none of them is at bus:%3$u device:%4$u"), - vendor, product, bus, device); - } else { - virReportError(VIR_ERR_OPERATION_FAILED, - _("Multiple USB devices for %1$x:%2$x, use <address> to specify one"), - vendor, product); - } + if (rc != 1 && autoAddress && device) { + VIR_INFO("USB device could not be found at previous address " + "(bus:%u device:%u)", bus, device); + + /* Second attempt, for when the device number has changed. */ + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + usbsrc->device = 0; + + rc = virHostdevFindUSBDeviceWithFlags(hostdev, mandatory, + flags, usb); + if (rc < 0) return -1; - }
- *usb = virUSBDeviceListGet(devs, 0); - virUSBDeviceListSteal(devs, *usb); + usbsrc->autoAddress = true; + }
+ if (!*usb) { + hostdev->missing = true; + } else if (!usbsrc->bus || !usbsrc->device) { usbsrc->bus = virUSBDeviceGetBus(*usb); usbsrc->device = virUSBDeviceGetDevno(*usb); - usbsrc->autoAddress = true; - - if (autoAddress) { - VIR_INFO("USB device %x:%x found at bus:%u device:%u (moved" - " from bus:%u device:%u)", - vendor, product, - usbsrc->bus, usbsrc->device, - bus, device); - } - } else if (bus) { - if (virUSBDeviceFindByBus(bus, device, NULL, mandatory, usb) < 0) - return -1; }
- out: - if (!*usb) - hostdev->missing = true; return 0; }
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a8ebf9efd8..cd7d00674f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3679,8 +3679,6 @@ virURIResolveAlias; # util/virusb.h virUSBDeviceFileIterate; virUSBDeviceFind; -virUSBDeviceFindByBus; -virUSBDeviceFindByVendor; virUSBDeviceFree; virUSBDeviceGetBus; virUSBDeviceGetDevno; diff --git a/src/util/virusb.c b/src/util/virusb.c index cf3461f80a..8aca190c4c 100644 --- a/src/util/virusb.c +++ b/src/util/virusb.c @@ -61,12 +61,6 @@ struct _virUSBDeviceList { virUSBDevice **devs; };
-typedef enum { - USB_DEVICE_ALL = 0, - USB_DEVICE_FIND_BY_VENDOR = 1 << 0, - USB_DEVICE_FIND_BY_BUS = 1 << 1, -} virUSBDeviceFindFlags; - static virClass *virUSBDeviceListClass;
static void virUSBDeviceListDispose(void *obj); @@ -102,11 +96,27 @@ 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) { @@ -127,6 +137,8 @@ virUSBDeviceSearch(unsigned int vendor,
while ((direrr = virDirRead(dir, &de, USB_SYSFS "/devices")) > 0) { unsigned int found_prod, found_vend, found_bus, found_devno; + char *found_port; + bool port_matches; char *tmpstr = de->d_name;
if (strchr(de->d_name, ':')) @@ -154,16 +166,32 @@ virUSBDeviceSearch(unsigned int vendor, 10, &found_devno) < 0) goto cleanup;
- if ((flags & USB_DEVICE_FIND_BY_VENDOR) && - (found_prod != product || found_vend != vendor)) - continue; + if (virUSBSysReadFileStr("devpath", de->d_name, + &found_port) < 0) { + goto cleanup; + } else { + found_port[strlen(found_port) - 1] = '\0'; /* remove newline */
virStringTrimOptionalNewline()
+ port_matches = STREQ_NULLABLE(found_port, port); + VIR_FREE(found_port); + }
- if (flags & USB_DEVICE_FIND_BY_BUS) { + if (flags & USB_DEVICE_FIND_BY_VENDOR) { + if (found_prod != product || found_vend != vendor) + continue; + } + + if (flags & USB_DEVICE_FIND_BY_DEVICE) { if (found_bus != bus || found_devno != devno) continue; 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)
Michal

On Mon, Jun 23, 2025 at 04:11:30PM +0200, Michal Prívozník via Devel wrote:
On 4/21/25 21:38, Maximilian Martin via Devel wrote:
From: Maximilian Martin <maximilian_martin@gmx.de>
This patch implements USB bus/port matching. In addition to vendor/product and bus/device matching, bus/port matching is implemented. This involves additions and refactorings in the domain configuration, host device handling, and USB helpers. Also, test methods are refactored and extended.
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- src/conf/domain_conf.c | 69 ++++++++++++++-- src/conf/domain_conf.h | 1 + src/hypervisor/virhostdev.c | 131 +++++++++++++++++------------- src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++++++++------------------------ src/util/virusb.h | 32 ++++---- tests/virusbtest.c | 149 ++++++++++++++++++++++++---------- 7 files changed, 312 insertions(+), 228 deletions(-)
There's still a lot happening here, but see my comments below.
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 542d6ade91..423cad99e5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2670,6 +2670,15 @@ virDomainHostdevSubsysSCSIClear(virDomainHostdevSubsysSCSI *scsisrc) } }
+static void +virDomainHostdevSubsysUSBClear(virDomainHostdevSubsysUSB *usbsrc) +{ + if (!usbsrc) + return; + + VIR_FREE(usbsrc->port); +} +
static void virDomainHostdevDefClear(virDomainHostdevDef *def) @@ -2713,6 +2722,8 @@ virDomainHostdevDefClear(virDomainHostdevDef *def) g_clear_pointer(&def->source.subsys.u.pci.origstates, virBitmapFree); break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: + virDomainHostdevSubsysUSBClear(&def->source.subsys.u.usb); + break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST: break; @@ -5945,13 +5956,47 @@ virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node, }
if ((addressNode = virXPathNode("./address", ctxt))) { - if (virXMLPropUInt(addressNode, "bus", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0) + bool found_device = false; + bool found_port = false;
Nitpick, we prefer cammelCase.
+ char *port = NULL; + int ret = -1;
The 'ret' name is reserved for actual return value of functions. For storing of retvals of other functions you can use 'rc', 'rv' or anything else.
+ + ret = virXMLPropUInt(addressNode, "bus", 0, + VIR_XML_PROP_REQUIRED, &usbsrc->bus); + if (ret < 0) { + return -1; + } else if (ret == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing bus")); return -1;
This case can never happen. When VIR_XML_PROP_REQUIRED flag is specified then virXMLProp*() functions return either -1 or 1.
+ }
- if (virXMLPropUInt(addressNode, "device", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->device) < 0) + ret = virXMLPropUInt(addressNode, "device", 0, + VIR_XML_PROP_NONE, &usbsrc->device); + if (ret < 0) return -1; + else if (ret > 0) + found_device = true; + + port = virXMLPropString(addressNode, "port"); + if (port) { + if (*port) { + usbsrc->port = port; + found_port = true; + } else { + VIR_FREE(port);
If 'port' is declared as g_autofree then this is needless.
+ } + } + + if (!found_device && !found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("usb address needs either device id or port")); + return -1; + } else if (found_device && found_port) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("found both device id and port in usb address (ambiguous setting)")); + return -1; + } }
return 0; @@ -14774,8 +14819,13 @@ virDomainHostdevMatchSubsysUSB(virDomainHostdevDef *first, virDomainHostdevSubsysUSB *first_usbsrc = &first->source.subsys.u.usb; virDomainHostdevSubsysUSB *second_usbsrc = &second->source.subsys.u.usb;
- if (first_usbsrc->bus && first_usbsrc->device) { - /* specified by bus location on host */ + if (first_usbsrc->bus && first_usbsrc->port) { + /* specified by bus and port on host */ + if (first_usbsrc->bus == second_usbsrc->bus && + STREQ_NULLABLE(first_usbsrc->port, second_usbsrc->port)) + return 1; + } else if (first_usbsrc->bus && first_usbsrc->device) { + /* specified by bus and device id on host */ if (first_usbsrc->bus == second_usbsrc->bus && first_usbsrc->device == second_usbsrc->device) return 1; @@ -24345,10 +24395,15 @@ virDomainHostdevDefFormatSubsysUSB(virBuffer *buf, virBufferAsprintf(&sourceChildBuf, "<product id='0x%.4x'/>\n", usbsrc->product); }
- if (usbsrc->bus || usbsrc->device) + if (usbsrc->bus && usbsrc->port) { + virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' port='%s'/>\n", + includeTypeInAddr ? "type='usb' " : "", + usbsrc->bus, usbsrc->port); + } else if (usbsrc->bus || usbsrc->device) { virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' device='%d'/>\n", includeTypeInAddr ? "type='usb' " : "", usbsrc->bus, usbsrc->device); + }
virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 58b97a2b54..aaa8b8573a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -228,6 +228,7 @@ struct _virDomainHostdevSubsysUSB { on vendor/product */ unsigned bus; unsigned device; + char *port;
unsigned vendor; unsigned product; diff --git a/src/hypervisor/virhostdev.c b/src/hypervisor/virhostdev.c index 0a1d8500d4..9e77477a9b 100644 --- a/src/hypervisor/virhostdev.c +++ b/src/hypervisor/virhostdev.c @@ -1358,6 +1358,42 @@ virHostdevMarkUSBDevices(virHostdevManager *mgr, return -1; }
+static int +virHostdevFindUSBDeviceWithFlags(virDomainHostdevDef *hostdev, + bool mandatory, + unsigned int flags, + virUSBDevice **usb) +{ + virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; + unsigned vendor = usbsrc->vendor; + unsigned product = usbsrc->product; + unsigned bus = usbsrc->bus; + char *port = usbsrc->port; + unsigned device = usbsrc->device; + virUSBDeviceList *devs;
g_autoptr(virUSBDeviceList) devs = NULL;
+ int rc; + + rc = virUSBDeviceFind(vendor, product, bus, device, port, NULL, + mandatory, flags, &devs); + if (rc < 0) + return -1; + + if (rc == 1) { + *usb = virUSBDeviceListGet(devs, 0); + virUSBDeviceListSteal(devs, *usb); + } + virObjectUnref(devs); + + if (rc > 1) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("Multiple USB devices for %1$x:%2$x, use <address> to specify one"), + vendor, product); + return -1; + } + + return rc; +} +
int virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, @@ -1366,77 +1402,64 @@ virHostdevFindUSBDevice(virDomainHostdevDef *hostdev, { virDomainHostdevSubsysUSB *usbsrc = &hostdev->source.subsys.u.usb; unsigned vendor = usbsrc->vendor; - unsigned product = usbsrc->product; unsigned bus = usbsrc->bus; unsigned device = usbsrc->device; + char *port = usbsrc->port; bool autoAddress = usbsrc->autoAddress; + unsigned int flags = 0; int rc;
*usb = NULL;
- if (vendor && bus) { - rc = virUSBDeviceFind(vendor, product, bus, device, - NULL, - autoAddress ? false : mandatory, - usb); - if (rc < 0) { - return -1; - } else if (!autoAddress) { - goto out; - } else { - VIR_INFO("USB device %x:%x could not be found at previous" - " address (bus:%u device:%u)", - vendor, product, bus, device); - } + if (vendor) + flags |= USB_DEVICE_FIND_BY_VENDOR; + if (device) + flags |= USB_DEVICE_FIND_BY_DEVICE; + if (port) + flags |= USB_DEVICE_FIND_BY_PORT; + + /* Rule out invalid cases. */ + if (vendor && device && port) { + VIR_WARN("Cannot match USB device on vendor/product, bus/device, and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE);
This typecasting is unnecessary. These values are already declared as non-negative values. And in C99 enums must fit into int. Everywhere else in our code we clear flags without typecasting.
+ } else if (device && port) { + VIR_WARN("Cannot match USB device on bus/device and bus/port at once. Ignoring bus/device."); + flags &= ~((unsigned int) USB_DEVICE_FIND_BY_DEVICE); + } else if (!vendor && !device && !port) { + VIR_WARN("No matching fields for USB device found. Vendor/product, bus/device, or bus/port required."); + return -1;
Since this returns an error the VIR_WARN() should be promoted to virReportError().
I'm wondering why we're ignoring invalid input for the previous two cases too ? Quietly ignoring incorrect user specified data is generally an anti-pattern we aim to avoid. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

From: Maximilian Martin <maximilian_martin@gmx.de> Adds USB bus/port addressing the domain XML file schema. Optionally, the physical USB port can be declared instead of the USB device address. Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- src/conf/schemas/domaincommon.rng | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 5597d5a66b..da6cf69efb 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -6709,9 +6709,14 @@ <attribute name="bus"> <ref name="usbAddr"/> </attribute> - <attribute name="device"> - <ref name="usbAddr"/> - </attribute> + <choice> + <attribute name="device"> + <ref name="usbAddr"/> + </attribute> + <attribute name="port"> + <ref name="usbPort"/> + </attribute> + </choice> </element> </define> <define name="scsiaddress"> -- 2.39.5

From: Maximilian Martin <maximilian_martin@gmx.de> Adds documentation for the new USB bus/port addressing. The new "port" attribute is explained. Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- docs/formatdomain.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index c7c75ae219..3cc0bb0533 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -4784,19 +4784,22 @@ or: tweak the loading process further using the ``bar`` or ``file`` attributes will be rejected. :since:`Since 4.3.0 (QEMU and KVM only)`. ``address`` - The ``address`` element for USB devices has a ``bus`` and ``device`` - attribute to specify the USB bus and device number the device appears at on - the host. The values of these attributes can be given in decimal, hexadecimal - (starting with 0x) or octal (starting with 0) form. For PCI devices the - element carries 4 attributes allowing to designate the device as can be found - with the ``lspci`` or with ``virsh nodedev-list``. For SCSI devices a 'drive' - address type must be used. For mediated devices, which are software-only - devices defining an allocation of resources on the physical parent device, - the address type used must conform to the ``model`` attribute of element - ``hostdev``, e.g. any address type other than PCI for ``vfio-pci`` device API - or any address type other than CCW for ``vfio-ccw`` device API will result in - an error. See the `Device Addresses`_ section for more details on the address - element. + The ``address`` element for USB devices has a ``bus`` attribute to specify + the USB bus. In addition, either a ``device`` attribute or a ``port`` + attribute is required to identify the device on the host. While the device + number is assigned upon connection of the device, the port number is a + stable identifier of the physical host port. Bus and device number can be + given in decimal, hexadecimal (starting with 0x) or octal (starting with 0) + form. The port number is a dotted path (examples: ``2``, ``1.2.5``). For PCI + devices the element carries 4 attributes allowing to designate the device as + can be found with the ``lspci`` or with ``virsh nodedev-list``. For SCSI + devices a 'drive' address type must be used. For mediated devices, which are + software-only devices defining an allocation of resources on the physical + parent device, the address type used must conform to the ``model`` attribute + of element ``hostdev``, e.g. any address type other than PCI for ``vfio-pci`` + device API or any address type other than CCW for ``vfio-ccw`` device API + will result in an error. See the `Device Addresses`_ section for more details + on the address element. ``driver`` PCI hostdev devices can have an optional ``driver`` subelement that specifies which host driver to bind to the device when preparing it -- 2.39.5

Friendly ping :) Am 21.04.2025 um 21:38 schrieb Maximilian Martin via Devel:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath

Bump :) Am 20.05.2025 um 20:49 schrieb Maximilian Martin:
Friendly ping :)
Am 21.04.2025 um 21:38 schrieb Maximilian Martin via Devel:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath

On 4/21/25 21:38, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath
I've accumulated some fixes to patch 2/4 and stored them as a fixup commit: https://gitlab.com/MichalPrivoznik/libvirt/-/commit/bd6f9c823b0bbdafce4a8b74... If you're fine with suggested changes I could squash them and merge. Michal

On Mon, Jun 23, 2025 at 04:11:25PM +0200, Michal Prívozník via Devel wrote:
On 4/21/25 21:38, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath
I've accumulated some fixes to patch 2/4 and stored them as a fixup commit:
https://gitlab.com/MichalPrivoznik/libvirt/-/commit/bd6f9c823b0bbdafce4a8b74...
If you're fine with suggested changes I could squash them and merge.
IMHO the series is incomplete as it has added new domain XML schema without adding any new test XML files to exercise it. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Am 23.06.2025 um 16:32 schrieb Daniel P. Berrangé via Devel:
On Mon, Jun 23, 2025 at 04:11:25PM +0200, Michal Prívozník via Devel wrote:
On 4/21/25 21:38, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath
I've accumulated some fixes to patch 2/4 and stored them as a fixup commit:
https://gitlab.com/MichalPrivoznik/libvirt/-/commit/bd6f9c823b0bbdafce4a8b74...
If you're fine with suggested changes I could squash them and merge. IMHO the series is incomplete as it has added new domain XML schema without adding any new test XML files to exercise it.
With regards, Daniel
I am not sure how to implement the XML test. Similar to vendor/product matching, I implemented bus/port matching as a "secondary" way of addressing. This means that libvirt will always translate the given address to a hostdevice path (e.g. /dev/bus/usb/014/006) with bus/device address. This path will be used for the qemu command. How can I implement an XML test when there is no real device which can be mapped?

On Thu, Jun 26, 2025 at 08:56:48AM +0200, Maximilian Martin wrote:
Am 23.06.2025 um 16:32 schrieb Daniel P. Berrangé via Devel:
On Mon, Jun 23, 2025 at 04:11:25PM +0200, Michal Prívozník via Devel wrote:
On 4/21/25 21:38, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
This patch is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H...
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513
Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de>
Maximilian Martin (4): virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute docs: add description for USB port matching
docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 69 +++++++- src/conf/domain_conf.h | 1 + src/conf/schemas/domaincommon.rng | 11 +- src/hypervisor/virhostdev.c | 131 +++++++++------ src/libvirt_private.syms | 2 - src/util/virusb.c | 156 ++++++------------ src/util/virusb.h | 32 ++-- tests/virusbtest.c | 149 ++++++++++++----- .../sys_bus_usb/devices/1-1.5.3.1/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.3/devpath | 1 + .../sys_bus_usb/devices/1-1.5.4/devpath | 1 + .../sys_bus_usb/devices/1-1.5.5/devpath | 1 + .../sys_bus_usb/devices/1-1.5.6/devpath | 1 + .../sys_bus_usb/devices/1-1.5/devpath | 1 + .../sys_bus_usb/devices/1-1.6/devpath | 1 + .../sys_bus_usb/devices/1-1/devpath | 1 + .../sys_bus_usb/devices/2-1.2/devpath | 1 + .../sys_bus_usb/devices/2-1/devpath | 1 + .../sys_bus_usb/devices/usb1/devpath | 1 + .../sys_bus_usb/devices/usb2/devpath | 1 + .../sys_bus_usb/devices/usb3/devpath | 1 + .../sys_bus_usb/devices/usb4/devpath | 1 + 24 files changed, 351 insertions(+), 244 deletions(-) create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.4/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.5/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1.6/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/1-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1.2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/2-1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb1/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb2/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb3/devpath create mode 100644 tests/virusbtestdata/sys_bus_usb/devices/usb4/devpath
I've accumulated some fixes to patch 2/4 and stored them as a fixup commit:
https://gitlab.com/MichalPrivoznik/libvirt/-/commit/bd6f9c823b0bbdafce4a8b74...
If you're fine with suggested changes I could squash them and merge. IMHO the series is incomplete as it has added new domain XML schema without adding any new test XML files to exercise it.
With regards, Daniel
I am not sure how to implement the XML test. Similar to vendor/product matching, I implemented bus/port matching as a "secondary" way of addressing. This means that libvirt will always translate the given address to a hostdevice path (e.g. /dev/bus/usb/014/006) with bus/device address. This path will be used for the qemu command. How can I implement an XML test when there is no real device which can be mapped?
I thought we had vendor/product matching tests, but I learnt that we don't. I then thought we could use the virhostusb mock from the virhostusb test case, but the XML tests won't exercise the callpaths for that. So I've just done a crude hack https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/7ZQYY... we could just extend that crude hack to at least illustrate the input XML parsing & formatting. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Mon, Apr 21, 2025 at 09:38:34PM +0200, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute.
In terms of our API, we're expecting people to use the 'node device' APIs to identify what devices are available on the host and their attributes, rather than queryin Linux directly. This gives us a platform independant usage model, which also works when the mgmt app has no login to the virt host. Currently for USB devices we report bus, dev, vendor & product, but don't report the port: # virsh nodedev-dumpxml usb_1_10_4 <device> <name>usb_1_10_4</name> <path>/sys/devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10.4</path> <devnode type='dev'>/dev/bus/usb/001/004</devnode> <parent>usb_1_10</parent> <driver> <name>usb</name> </driver> <capability type='usb_device'> <bus>1</bus> <device>4</device> <product id='0x10c0'>Dell Integrated Hub</product> <vendor id='0x1604'>Tascam</vendor> </capability> </device> we need to add 'port' to this xml document under the 'usb_device' capability. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Am 23.06.2025 um 16:50 schrieb Daniel P. Berrangé:
On Mon, Apr 21, 2025 at 09:38:34PM +0200, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute. In terms of our API, we're expecting people to use the 'node device' APIs to identify what devices are available on the host and their attributes, rather than queryin Linux directly. This gives us a platform independant usage model, which also works when the mgmt app has no login to the virt host.
Currently for USB devices we report bus, dev, vendor & product, but don't report the port:
# virsh nodedev-dumpxml usb_1_10_4 <device> <name>usb_1_10_4</name> <path>/sys/devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10.4</path> <devnode type='dev'>/dev/bus/usb/001/004</devnode> <parent>usb_1_10</parent> <driver> <name>usb</name> </driver> <capability type='usb_device'> <bus>1</bus> <device>4</device> <product id='0x10c0'>Dell Integrated Hub</product> <vendor id='0x1604'>Tascam</vendor> </capability> </device>
we need to add 'port' to this xml document under the 'usb_device' capability.
With regards, Daniel
Well, the port is included in the path (everything after last '-'). udev does not report it explicitly. Do you think this is enough or should I parse the path and add the port to capabilites?

On Thu, Jun 26, 2025 at 06:01:30PM +0200, Maximilian Martin wrote:
Am 23.06.2025 um 16:50 schrieb Daniel P. Berrangé:
On Mon, Apr 21, 2025 at 09:38:34PM +0200, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute. In terms of our API, we're expecting people to use the 'node device' APIs to identify what devices are available on the host and their attributes, rather than queryin Linux directly. This gives us a platform independant usage model, which also works when the mgmt app has no login to the virt host.
Currently for USB devices we report bus, dev, vendor & product, but don't report the port:
# virsh nodedev-dumpxml usb_1_10_4 <device> <name>usb_1_10_4</name> <path>/sys/devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10.4</path> <devnode type='dev'>/dev/bus/usb/001/004</devnode> <parent>usb_1_10</parent> <driver> <name>usb</name> </driver> <capability type='usb_device'> <bus>1</bus> <device>4</device> <product id='0x10c0'>Dell Integrated Hub</product> <vendor id='0x1604'>Tascam</vendor> </capability> </device>
we need to add 'port' to this xml document under the 'usb_device' capability.
Well, the port is included in the path (everything after last '-'). udev does not report it explicitly. Do you think this is enough or should I parse the path and add the port to capabilites?
No, we would consider the path to be a platform specific opaque string, and liable to change. So it isn't something apps should be parsing to extract info from. We need the path explicitly modelled under the usb_device capability With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Thanks, Daniel and Michal, for your review comments. I have incorporated all comments and will submit a new patchset once Daniel's patch is merged ("tests: validate an XML config with USB vendor/product set"). Am 26.06.2025 um 18:07 schrieb Daniel P. Berrangé via Devel:
On Thu, Jun 26, 2025 at 06:01:30PM +0200, Maximilian Martin wrote:
Am 23.06.2025 um 16:50 schrieb Daniel P. Berrangé:
On Mon, Apr 21, 2025 at 09:38:34PM +0200, Maximilian Martin via Devel wrote:
This resubmission splits up the previous patch into multiple patches and incorporates review comments from Michal Prívozník.
Currently, only vendor/product and bus/device matching are supported for USB host devices. Neither of these provide a stable and persistent way of assigning a guest a specific host device. Vendor/product can be ambiguous. Device numbers change on every enumeration.
This patch adds a bus/port matching, which allows a specific port on the host to be specified using the dotted notation found in Linux's "devpath" sysfs attribute. In terms of our API, we're expecting people to use the 'node device' APIs to identify what devices are available on the host and their attributes, rather than queryin Linux directly. This gives us a platform independant usage model, which also works when the mgmt app has no login to the virt host.
Currently for USB devices we report bus, dev, vendor & product, but don't report the port:
# virsh nodedev-dumpxml usb_1_10_4 <device> <name>usb_1_10_4</name> <path>/sys/devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10.4</path> <devnode type='dev'>/dev/bus/usb/001/004</devnode> <parent>usb_1_10</parent> <driver> <name>usb</name> </driver> <capability type='usb_device'> <bus>1</bus> <device>4</device> <product id='0x10c0'>Dell Integrated Hub</product> <vendor id='0x1604'>Tascam</vendor> </capability> </device>
we need to add 'port' to this xml document under the 'usb_device' capability.
Well, the port is included in the path (everything after last '-'). udev does not report it explicitly. Do you think this is enough or should I parse the path and add the port to capabilites?
No, we would consider the path to be a platform specific opaque string, and liable to change. So it isn't something apps should be parsing to extract info from.
We need the path explicitly modelled under the usb_device capability
With regards, Daniel
Teilnehmer (3)
-
Daniel P. Berrangé
-
Maximilian Martin
-
Michal Prívozník