
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 :|