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(a)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(a)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 :|