This is the last piece for a full fledged PCI multifunction
partial assignment support. Now that we have a defined XML format
and QEMU is ignoring the existence of the hostdev that isn't
being assigned, let's enforce that all functions must be declared
in the domain XML for such devices. This will make sure that
we'll always detach/re-attach the whole IOMMU and will force
the user to be aware of what is going to be detached from
the host.
Signed-off-by: Daniel Henrique Barboza <danielhb413(a)gmail.com>
---
src/util/virhostdev.c | 64 ++++++++++++++++++-
...ostdev-pci-multifunction-partial-fail.args | 31 +++++++++
...hostdev-pci-multifunction-partial-fail.xml | 35 ++++++++++
tests/qemuxml2argvtest.c | 4 ++
4 files changed, 132 insertions(+), 2 deletions(-)
create mode 100644 tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.args
create mode 100644 tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.xml
diff --git a/src/util/virhostdev.c b/src/util/virhostdev.c
index 1aa8e9729d..a85860b34f 100644
--- a/src/util/virhostdev.c
+++ b/src/util/virhostdev.c
@@ -56,6 +56,13 @@ struct virHostdevIsPCINodeDeviceUsedData {
bool usesVFIO;
};
+struct virHostdevIsAllIOMMUGroupUsedData {
+ virPCIDeviceListPtr pcidevs;
+ const char *domainName;
+ const char *deviceName;
+};
+
+
/* This module makes heavy use of bookkeeping lists contained inside a
* virHostdevManager instance to keep track of the devices' status. To make
* it easy to spot potential ownership errors when moving devices from one
@@ -114,6 +121,26 @@ static int virHostdevIsPCINodeDeviceUsed(virPCIDeviceAddressPtr
devAddr, void *o
return ret;
}
+static int virHostdevIsAllIOMMUGroupUsed(virPCIDeviceAddressPtr devAddr, void *opaque)
+{
+ struct virHostdevIsAllIOMMUGroupUsedData *helperData = opaque;
+ virPCIDevicePtr actual;
+
+ actual = virPCIDeviceListFindByIDs(helperData->pcidevs,
+ devAddr->domain, devAddr->bus,
+ devAddr->slot, devAddr->function);
+ if (actual) {
+ return 0;
+ } else {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ _("All devices of the same IOMMU group as the "
+ "multifunction PCI device %s must belong to "
+ "domain %s"),
+ helperData->deviceName, helperData->domainName);
+ return -1;
+ }
+}
+
static int virHostdevManagerOnceInit(void)
{
if (!VIR_CLASS_NEW(virHostdevManager, virClassForObject()))
@@ -714,9 +741,10 @@ virHostdevPreparePCIDevices(virHostdevManagerPtr mgr,
unsigned int flags)
{
VIR_AUTOUNREF(virPCIDeviceListPtr) pcidevs = NULL;
+ VIR_AUTOFREE(unsigned int *) searchedIOMMUs = NULL;
int last_processed_hostdev_vf = -1;
- size_t i;
- int ret = -1;
+ size_t i, j;
+ int ret = -1, nSearchedIOMMUs = 0;
virPCIDeviceAddressPtr devAddr = NULL;
if (!nhostdevs)
@@ -725,6 +753,9 @@ virHostdevPreparePCIDevices(virHostdevManagerPtr mgr,
if (!(pcidevs = virHostdevGetPCIHostDeviceList(hostdevs, nhostdevs)))
return -1;
+ if (VIR_ALLOC_N(searchedIOMMUs, virPCIDeviceListCount(pcidevs)))
+ return -1;
+
virObjectLock(mgr->activePCIHostdevs);
virObjectLock(mgr->inactivePCIHostdevs);
@@ -767,6 +798,35 @@ virHostdevPreparePCIDevices(virHostdevManagerPtr mgr,
if (virHostdevIsPCINodeDeviceUsed(devAddr, &data))
goto cleanup;
+ /* Multifunction PCI devices are more restrict than regular
+ * VFIO devices, since it requires *all* devices from the same
+ * IOMMU to belong to the same domain - even if not all of
+ * them are assigned to the guest.
+ */
+ if (virPCIDeviceIsMultifunction(pci)) {
+ struct virHostdevIsAllIOMMUGroupUsedData helper = {
+ pcidevs, dom_name, virPCIDeviceGetName(pci)};
+ int devIOMMUGroup = virPCIDeviceAddressGetIOMMUGroupNum(devAddr);
+ bool alreadySearched = false;
+
+ for (j = 0; j < nSearchedIOMMUs; j++) {
+ if (devIOMMUGroup == searchedIOMMUs[j]) {
+ alreadySearched = true;
+ break;
+ }
+ }
+
+ if (alreadySearched)
+ continue;
+
+ if (virPCIDeviceAddressIOMMUGroupIterate(
+ devAddr, virHostdevIsAllIOMMUGroupUsed, &helper) < 0)
+ goto cleanup;
+
+ searchedIOMMUs[nSearchedIOMMUs++] = devIOMMUGroup;
+ continue;
+ }
+
/* VFIO devices belonging to same IOMMU group can't be
* shared across guests. Check if that's the case. */
if (usesVFIO) {
diff --git a/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.args
b/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.args
new file mode 100644
index 0000000000..4074968c84
--- /dev/null
+++ b/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.args
@@ -0,0 +1,31 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-delete \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-delete/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-delete/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-delete/.config \
+QEMU_AUDIO_DRV=none \
+/usr/bin/qemu-system-x86_64 \
+-name delete \
+-S \
+-machine pc,accel=kvm,usb=off,dump-guest-core=off \
+-m 256 \
+-realtime mlock=off \
+-smp 4,sockets=4,cores=1,threads=1 \
+-uuid 583a8e8e-f0ce-4f53-89ab-092862148b25 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,path=/tmp/lib/domain--1-delete/monitor.sock,\
+server,nowait \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-usb \
+-device vfio-pci,host=0005:90:01.0,id=hostdev0,bus=pci.0,addr=0x3 \
+-device vfio-pci,host=0005:90:01.1,id=hostdev1,bus=pci.0,addr=0x4 \
+-device vfio-pci,host=0005:90:01.3,id=hostdev2,bus=pci.0,addr=0x5 \
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x6
diff --git a/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.xml
b/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.xml
new file mode 100644
index 0000000000..fc3e3bb520
--- /dev/null
+++ b/tests/qemuxml2argvdata/hostdev-pci-multifunction-partial-fail.xml
@@ -0,0 +1,35 @@
+<domain type='kvm'>
+ <name>delete</name>
+ <uuid>583a8e8e-f0ce-4f53-89ab-092862148b25</uuid>
+ <memory unit='KiB'>262144</memory>
+ <vcpu placement='static'>4</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ </os>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <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='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x0'/>
+ </source>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x1'/>
+ </source>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x3'/>
+ </source>
+ </hostdev>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 434e01308c..462852c690 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -1361,6 +1361,10 @@ mymain(void)
QEMU_CAPS_KVM,
QEMU_CAPS_DEVICE_VFIO_PCI);
+ DO_TEST("hostdev-pci-multifunction-partial-fail",
+ QEMU_CAPS_KVM,
+ QEMU_CAPS_DEVICE_VFIO_PCI);
+
DO_TEST("serial-file-log",
QEMU_CAPS_CHARDEV_FILE_APPEND,
QEMU_CAPS_DEVICE_ISA_SERIAL,
--
2.21.0