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

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. The path series is based on the previous work of Thomas Hebb: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/7U3H... This resubmission includes Daniel's patch which extends the USB hostdev test logic. Maximilian Martin (7): tests: validate an XML config with USB vendor/product set virusb test data: add devpath files for port addressing domain_conf, virhostdev, virusb, virusb test: add bus/port matching schema: add USB port attribute tests: validate an XML config with USB bus/port set nodedev: add USB port to nodedev XML docs: add description for USB port matching docs/formatdomain.rst | 29 ++-- src/conf/domain_conf.c | 58 ++++++- src/conf/domain_conf.h | 1 + src/conf/node_device_conf.c | 5 + src/conf/node_device_conf.h | 1 + src/conf/schemas/basictypes.rng | 31 ++++ src/conf/schemas/domaincommon.rng | 41 +---- src/conf/schemas/nodedev.rng | 3 + src/hypervisor/virhostdev.c | 131 ++++++++------ src/libvirt_private.syms | 2 - src/node_device/node_device_udev.c | 4 + src/util/virusb.c | 160 ++++++------------ src/util/virusb.h | 22 +-- .../usb_device_1d6b_1_0000_00_1d_0.xml | 1 + ...ostdev-usb-address-port.x86_64-latest.args | 36 ++++ ...hostdev-usb-address-port.x86_64-latest.xml | 45 +++++ .../hostdev-usb-address-port.xml | 28 +++ ...tdev-usb-vendor-product.x86_64-latest.args | 35 ++++ ...stdev-usb-vendor-product.x86_64-latest.xml | 44 +++++ .../hostdev-usb-vendor-product.xml | 36 ++++ tests/qemuxmlconftest.c | 25 +++ 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 + 37 files changed, 631 insertions(+), 271 deletions(-) create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.xml create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.xml 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> The USB vendor/product is usually translated into a device/bus at startup using the hostdev logic. We don't run the latter in the unit test suite, but we can fake it by hardcoding a translation. This demonstrates that we format the command line with the normal device/bus properties, even when vendor/product is set. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- ...tdev-usb-vendor-product.x86_64-latest.args | 35 +++++++++++++++ ...stdev-usb-vendor-product.x86_64-latest.xml | 44 +++++++++++++++++++ .../hostdev-usb-vendor-product.xml | 36 +++++++++++++++ tests/qemuxmlconftest.c | 18 ++++++++ 4 files changed, 133 insertions(+) create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/hostdev-usb-vendor-product.xml diff --git a/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.args b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.args new file mode 100644 index 0000000000..62338db872 --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.args @@ -0,0 +1,35 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"usb-host","hostdevice":"/dev/bus/usb/042/4660","id":"hostdev0","guest-reset":true,"guest-resets-all":false,"bus":"usb.0","port":"1"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.xml b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.xml new file mode 100644 index 0000000000..340df80263 --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.x86_64-latest.xml @@ -0,0 +1,44 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <hostdev mode='subsystem' type='usb' managed='no'> + <source guestReset='uninitialized'> + <vendor id='0x1234'/> + <product id='0x4321'/> + </source> + </hostdev> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/hostdev-usb-vendor-product.xml b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.xml new file mode 100644 index 0000000000..dfb668f208 --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-vendor-product.xml @@ -0,0 +1,36 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <hostdev mode='subsystem' type='usb' managed='no'> + <source guestReset='uninitialized'> + <vendor id='0x1234'/> + <product id='0x4321'/> + </source> + </hostdev> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 079f20ddf4..56ace476af 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -474,6 +474,23 @@ testCompareXMLToArgvCreateArgs(virQEMUDriver *drv, } } + for (i = 0; i < vm->def->nhostdevs; i++) { + virDomainHostdevDef *hostdev = vm->def->hostdevs[i]; + + if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && + hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { + virDomainHostdevSubsysUSB *usb = &hostdev->source.subsys.u.usb; + if (!usb->device && !usb->bus) { + if (usb->vendor == 0x1234 && usb->product == 0x4321) { + usb->bus = 42; + usb->device = 0x1234; + } else { + g_assert_not_reached(); + } + } + } + } + if (flags & FLAG_SLIRP_HELPER) { for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; @@ -2143,6 +2160,7 @@ mymain(void) DO_TEST_CAPS_LATEST("hostdev-usb-address-device"); DO_TEST_CAPS_LATEST("hostdev-usb-address-device-boot"); DO_TEST_CAPS_LATEST_PARSE_ERROR("hostdev-usb-duplicate"); + DO_TEST_CAPS_LATEST("hostdev-usb-vendor-product"); DO_TEST_CAPS_LATEST("hostdev-pci-address"); DO_TEST_CAPS_LATEST("hostdev-pci-address-device"); DO_TEST_CAPS_LATEST_PARSE_ERROR("hostdev-pci-duplicate"); -- 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 | 58 +++++++++++-- src/conf/domain_conf.h | 1 + src/hypervisor/virhostdev.c | 131 +++++++++++++++++------------ src/libvirt_private.syms | 2 - src/util/virusb.c | 160 ++++++++++++------------------------ src/util/virusb.h | 22 ++--- tests/virusbtest.c | 149 +++++++++++++++++++++++---------- 7 files changed, 298 insertions(+), 225 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 7766e302ec..63f3032892 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2680,6 +2680,15 @@ virDomainHostdevSubsysSCSIClear(virDomainHostdevSubsysSCSI *scsisrc) } } +static void +virDomainHostdevSubsysUSBClear(virDomainHostdevSubsysUSB *usbsrc) +{ + if (!usbsrc) + return; + + VIR_FREE(usbsrc->port); +} + static void virDomainHostdevDefClear(virDomainHostdevDef *def) @@ -2723,6 +2732,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; @@ -5961,13 +5972,38 @@ virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node, } if ((addressNode = virXPathNode("./address", ctxt))) { + bool foundDevice = false; + bool foundPort = false; + g_autofree char *port = NULL; + int rc = -1; + if (virXMLPropUInt(addressNode, "bus", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0) + VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0) { return -1; + } - if (virXMLPropUInt(addressNode, "device", 0, - VIR_XML_PROP_REQUIRED, &usbsrc->device) < 0) + rc = virXMLPropUInt(addressNode, "device", 0, + VIR_XML_PROP_NONE, &usbsrc->device); + if (rc < 0) return -1; + else if (rc > 0) + foundDevice = true; + + port = virXMLPropString(addressNode, "port"); + if (port && *port) { + usbsrc->port = g_steal_pointer(&port); + foundPort = true; + } + + if (!foundDevice && !foundPort) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("usb address needs either device id or port")); + return -1; + } else if (foundDevice && foundPort) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("found both device id and port in usb address (ambiguous setting)")); + return -1; + } } return 0; @@ -14851,8 +14887,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; @@ -24511,10 +24552,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 eca820892e..a655dc57a8 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 7d7df4418d..19907c76ba 100644 --- a/src/hypervisor/virhostdev.c +++ b/src/hypervisor/virhostdev.c @@ -1359,6 +1359,41 @@ 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; + const char *port = usbsrc->port; + unsigned device = usbsrc->device; + 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); + } + + 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, @@ -1367,77 +1402,65 @@ 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; + const 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) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Cannot match USB device on vendor/product, bus/device, and bus/port at once.")); + } else if (device && port) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Cannot match USB device on bus/device and bus/port at once.")); + } else if (!vendor && !device && !port) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("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 &= ~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 b846011f0f..bc01f25d4c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3680,8 +3680,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..85ad3d58ce 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,29 @@ static int virUSBSysReadFile(const char *f_name, const char *d_name, return 0; } +static int +virUSBSysReadFileStr(const char *f_name, + const char *d_name, + char **value) +{ + char *buf = NULL; + g_autofree char *filename = NULL; + + filename = g_strdup_printf(USB_SYSFS "/devices/%s/%s", d_name, f_name); + + if (virFileReadAll(filename, 1024, &buf) < 0) + return -1; + + *value = buf; + return 0; +} + static virUSBDeviceList * virUSBDeviceSearch(unsigned int vendor, unsigned int product, unsigned int bus, unsigned int devno, + const char *port, const char *vroot, unsigned int flags) { @@ -127,6 +139,8 @@ virUSBDeviceSearch(unsigned int vendor, while ((direrr = virDirRead(dir, &de, USB_SYSFS "/devices")) > 0) { unsigned int found_prod, found_vend, found_bus, found_devno; + g_autofree char *found_port = NULL; + bool port_matches; char *tmpstr = de->d_name; if (strchr(de->d_name, ':')) @@ -154,16 +168,31 @@ 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 { + virStringTrimOptionalNewline(found_port); + port_matches = STREQ_NULLABLE(found_port, 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) @@ -185,123 +214,42 @@ virUSBDeviceSearch(unsigned int vendor, return ret; } -int -virUSBDeviceFindByVendor(unsigned int vendor, - unsigned int product, - const char *vroot, - bool mandatory, - virUSBDeviceList **devices) -{ - virUSBDeviceList *list; - int count; - - if (!(list = virUSBDeviceSearch(vendor, product, 0, 0, - vroot, - USB_DEVICE_FIND_BY_VENDOR))) - return -1; - - if (list->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); - return -1; - } - - count = list->count; - if (devices) - *devices = list; - else - virObjectUnref(list); - - 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 *port, const char *vroot, bool mandatory, - virUSBDevice **usb) + unsigned int flags, + virUSBDeviceList **devices) { - virUSBDeviceList *list; + g_autoptr(virUSBDeviceList) list = NULL; + int count; - unsigned int flags = USB_DEVICE_FIND_BY_VENDOR|USB_DEVICE_FIND_BY_BUS; - if (!(list = virUSBDeviceSearch(vendor, product, bus, devno, + if (!(list = virUSBDeviceSearch(vendor, product, bus, devno, port, vroot, flags))) return -1; - if (list->count == 0) { - virObjectUnref(list); + count = list->count; + if (count == 0) { if (!mandatory) { - VIR_DEBUG("Did not find USB device %04x:%04x bus:%u device:%u", - vendor, product, bus, devno); - if (usb) - *usb = NULL; + if (devices) + *devices = 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); + _("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; } - if (usb) { - *usb = virUSBDeviceListGet(list, 0); - virUSBDeviceListSteal(list, *usb); - } - virObjectUnref(list); + if (devices) + *devices = g_steal_pointer(&list); - return 0; + return count; } virUSBDevice * diff --git a/src/util/virusb.h b/src/util/virusb.h index d2b3f69942..86cc0a9d3d 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 *port, const char *vroot, bool mandatory, - virUSBDevice **usb); + 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..12ac338df9 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

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 e369fb6e81..438bdc6ef6 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -6785,9 +6785,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> USB bus/port addressing is translated into a bus/device addressing at startup using the hostdev logic. This test covers XML parsing and CLI formatting for bus/port addressing. Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- ...ostdev-usb-address-port.x86_64-latest.args | 36 +++++++++++++++ ...hostdev-usb-address-port.x86_64-latest.xml | 45 +++++++++++++++++++ .../hostdev-usb-address-port.xml | 28 ++++++++++++ tests/qemuxmlconftest.c | 7 +++ 4 files changed, 116 insertions(+) create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/hostdev-usb-address-port.xml diff --git a/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.args b/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.args new file mode 100644 index 0000000000..8996f05223 --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"usb-host","hostdevice":"/dev/bus/usb/002/004","id":"hostdev0","bus":"usb.0","port":"1"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.xml b/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.xml new file mode 100644 index 0000000000..7a280d9ecd --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-address-port.x86_64-latest.xml @@ -0,0 +1,45 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <hostdev mode='subsystem' type='usb' managed='no'> + <source> + <address bus='2' port='3'/> + </source> + </hostdev> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/hostdev-usb-address-port.xml b/tests/qemuxmlconfdata/hostdev-usb-address-port.xml new file mode 100644 index 0000000000..199cf9c372 --- /dev/null +++ b/tests/qemuxmlconfdata/hostdev-usb-address-port.xml @@ -0,0 +1,28 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + </disk> + <hostdev mode='subsystem' type='usb' managed='no'> + <source> + <address bus='2' port='3'/> + </source> + </hostdev> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 56ace476af..4111d21853 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -487,6 +487,12 @@ testCompareXMLToArgvCreateArgs(virQEMUDriver *drv, } else { g_assert_not_reached(); } + } else if (!usb->device && !usb->vendor && !usb->product) { + if (usb->bus == 2 && STREQ(usb->port, "3")) { + usb->device = 4; + } else { + g_assert_not_reached(); + } } } } @@ -2161,6 +2167,7 @@ mymain(void) DO_TEST_CAPS_LATEST("hostdev-usb-address-device-boot"); DO_TEST_CAPS_LATEST_PARSE_ERROR("hostdev-usb-duplicate"); DO_TEST_CAPS_LATEST("hostdev-usb-vendor-product"); + DO_TEST_CAPS_LATEST("hostdev-usb-address-port"); DO_TEST_CAPS_LATEST("hostdev-pci-address"); DO_TEST_CAPS_LATEST("hostdev-pci-address-device"); DO_TEST_CAPS_LATEST_PARSE_ERROR("hostdev-pci-duplicate"); -- 2.39.5

From: Maximilian Martin <maximilian_martin@gmx.de> This adds the physical USB port to the capabilities of a USB device in nodedev XML. example: <port>1.4</port> Signed-off-by: Maximilian Martin <maximilian_martin@gmx.de> --- src/conf/node_device_conf.c | 5 +++ src/conf/node_device_conf.h | 1 + src/conf/schemas/basictypes.rng | 31 +++++++++++++++++++ src/conf/schemas/domaincommon.rng | 30 ------------------ src/conf/schemas/nodedev.rng | 3 ++ src/node_device/node_device_udev.c | 4 +++ .../usb_device_1d6b_1_0000_00_1d_0.xml | 1 + 7 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index 9c7982c680..ed0d340aa2 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -432,6 +432,9 @@ virNodeDeviceCapUSBDevDefFormat(virBuffer *buf, virBufferAsprintf(buf, "<bus>%d</bus>\n", data->usb_dev.bus); virBufferAsprintf(buf, "<device>%d</device>\n", data->usb_dev.device); + if (data->usb_dev.port) + virBufferEscapeString(buf, "<port>%s</port>\n", + data->usb_dev.port); virBufferAsprintf(buf, "<product id='0x%04x'", data->usb_dev.product); if (data->usb_dev.product_name) @@ -2083,6 +2086,7 @@ virNodeDevCapUSBDevParseXML(xmlXPathContextPtr ctxt, _("invalid USB product ID supplied for '%1$s'")) < 0) return -1; + usb_dev->port = virXPathString("string(./port[1])", ctxt); usb_dev->vendor_name = virXPathString("string(./vendor[1])", ctxt); usb_dev->product_name = virXPathString("string(./product[1])", ctxt); @@ -2802,6 +2806,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps) case VIR_NODE_DEV_CAP_USB_DEV: g_free(data->usb_dev.product_name); g_free(data->usb_dev.vendor_name); + g_free(data->usb_dev.port); break; case VIR_NODE_DEV_CAP_USB_INTERFACE: g_free(data->usb_if.description); diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index b98fb750ce..d6d2081278 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -211,6 +211,7 @@ typedef struct _virNodeDevCapUSBDev virNodeDevCapUSBDev; struct _virNodeDevCapUSBDev { unsigned int bus; unsigned int device; + char *port; unsigned int product; unsigned int vendor; char *product_name; diff --git a/src/conf/schemas/basictypes.rng b/src/conf/schemas/basictypes.rng index 2931e316b7..7d0361b413 100644 --- a/src/conf/schemas/basictypes.rng +++ b/src/conf/schemas/basictypes.rng @@ -378,6 +378,37 @@ </choice> </define> + <define name="usbIdDefault"> + <data type="string"> + <param name="pattern">-1</param> + </data> + </define> + <define name="usbId"> + <data type="string"> + <param name="pattern">(0x)?[0-9a-fA-F]{1,4}</param> + </data> + </define> + <define name="usbVersion"> + <data type="string"> + <param name="pattern">[0-9]{1,2}.[0-9]{1,2}</param> + </data> + </define> + <define name="usbAddr"> + <data type="string"> + <param name="pattern">(0x)?[0-9a-fA-F]{1,3}</param> + </data> + </define> + <define name="usbClass"> + <data type="string"> + <param name="pattern">(0x)?[0-9a-fA-F]{1,2}</param> + </data> + </define> + <define name="usbPort"> + <data type="string"> + <param name="pattern">((0x)?[0-9a-fA-F]{1,3}\.){0,3}(0x)?[0-9a-fA-F]{1,3}</param> + </data> + </define> + <define name="wwn"> <data type="string"> <param name="pattern">(0x)?[0-9a-fA-F]{16}</param> diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 438bdc6ef6..ee91bf7133 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -8686,36 +8686,6 @@ <ref name="dnsName"/> </choice> </define> - <define name="usbIdDefault"> - <data type="string"> - <param name="pattern">-1</param> - </data> - </define> - <define name="usbId"> - <data type="string"> - <param name="pattern">(0x)?[0-9a-fA-F]{1,4}</param> - </data> - </define> - <define name="usbVersion"> - <data type="string"> - <param name="pattern">[0-9]{1,2}.[0-9]{1,2}</param> - </data> - </define> - <define name="usbAddr"> - <data type="string"> - <param name="pattern">(0x)?[0-9a-fA-F]{1,3}</param> - </data> - </define> - <define name="usbClass"> - <data type="string"> - <param name="pattern">(0x)?[0-9a-fA-F]{1,2}</param> - </data> - </define> - <define name="usbPort"> - <data type="string"> - <param name="pattern">((0x)?[0-9a-fA-F]{1,3}\.){0,3}(0x)?[0-9a-fA-F]{1,3}</param> - </data> - </define> <define name="driveController"> <data type="string"> <param name="pattern">[0-9]{1,2}</param> diff --git a/src/conf/schemas/nodedev.rng b/src/conf/schemas/nodedev.rng index 31ce517e4d..3b5d1391c5 100644 --- a/src/conf/schemas/nodedev.rng +++ b/src/conf/schemas/nodedev.rng @@ -288,6 +288,9 @@ <element name="device"> <ref name="unsignedLong"/> </element> + <element name="port"> + <ref name="unsignedLong"/> + </element> <element name="product"> <attribute name="id"> diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c index 30c2ddf568..1f1f84c6ca 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -595,6 +595,10 @@ udevProcessUSBDevice(struct udev_device *device, return -1; if (udevGetUintProperty(device, "ID_VENDOR_ID", &usb_dev->vendor, 16) < 0) return -1; + if (!usb_dev->port) { + udevGetStringSysfsAttr(device, "devpath", + &usb_dev->port); + } udevGetStringProperty(device, "ID_VENDOR_FROM_DATABASE", diff --git a/tests/nodedevschemadata/usb_device_1d6b_1_0000_00_1d_0.xml b/tests/nodedevschemadata/usb_device_1d6b_1_0000_00_1d_0.xml index 29533e769b..0898213532 100644 --- a/tests/nodedevschemadata/usb_device_1d6b_1_0000_00_1d_0.xml +++ b/tests/nodedevschemadata/usb_device_1d6b_1_0000_00_1d_0.xml @@ -4,6 +4,7 @@ <capability type='usb_device'> <bus>2</bus> <device>1</device> + <port>3</port> <product id='0x0001'>1.1 root hub</product> <vendor id='0x1d6b'>Linux Foundation</vendor> </capability> -- 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. Resolves: https://gitlab.com/libvirt/libvirt/-/issues/513 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 9f7311b6d5..687d00c56b 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -4837,19 +4837,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
Teilnehmer (2)
-
Maximilian Martin
-
Maximilian Martin