From: Shivaprasad G Bhat <sbhat(a)linux.vnet.ibm.com>
The support to allow multifunction device hotplug consists
of using virDomainDeviceDefListPtr lists instead of single
virDomainDeviceDefPtr device definitions in both
qemuDomainAttachDeviceConfig() and qemuDomainAttachDeviceLive().
In AttachDeviceConfig() the same existing single device code
is ran for all devs in the list.
AttachDeviceLive() will verify if the list has size = 1 (meaning it
is a single device attach) and redirect the code to the regular single
dev attach procedure. If list size > 1, we'll execute a new specialized
function called qemuDomainAttachMultifunctionDevice(), which is
prepared to handle the nuances of this type of hotplug.
The changes in both AttachDeviceLive() and AttachDeviceConfig()
implied in changes to be made in qemuDomainAttachDeviceLiveAndConfig(),
which is now handling a device list instead of a single device.
No changes in regular device hotplug mechanics were made.
Signed-off-by: Shivaprasad G Bhat <sbhat(a)linux.vnet.ibm.com>
Signed-off-by: Daniel Henrique Barboza <danielhb413(a)gmail.com>
---
src/qemu/qemu_domain_address.c | 74 +++++++++++
src/qemu/qemu_domain_address.h | 4 +
src/qemu/qemu_driver.c | 119 +++++++++++-------
src/qemu/qemu_hotplug.c | 86 +++++++++++++
src/qemu/qemu_hotplug.h | 4 +
tests/qemuhotplugtest.c | 39 ++++--
...emuhotplug-multifunction-hostdev-pci-2.xml | 14 +++
.../qemuhotplug-multifunction-hostdev-pci.xml | 26 ++++
...ug-base-live+multifunction-hostdev-pci.xml | 82 ++++++++++++
...-base-live+multifunction-hostdev-pci-2.xml | 59 +++++++++
...es-base-live+multifunction-hostdev-pci.xml | 69 ++++++++++
11 files changed, 524 insertions(+), 52 deletions(-)
create mode 100644
tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci-2.xml
create mode 100644
tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci.xml
create mode 100644
tests/qemuhotplugtestdomains/qemuhotplug-base-live+multifunction-hostdev-pci.xml
create mode 100644
tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci-2.xml
create mode 100644
tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci.xml
diff --git a/src/qemu/qemu_domain_address.c b/src/qemu/qemu_domain_address.c
index 05ef4de100..94d91a5daf 100644
--- a/src/qemu/qemu_domain_address.c
+++ b/src/qemu/qemu_domain_address.c
@@ -23,6 +23,7 @@
#include "qemu_domain_address.h"
#include "qemu_domain.h"
+#include "domain_conf.h"
#include "viralloc.h"
#include "virhostdev.h"
#include "virerror.h"
@@ -3439,6 +3440,79 @@ qemuDomainEnsurePCIAddress(virDomainObjPtr obj,
info->pciConnectFlags);
}
+
+int
+qemuDomainPCIMultifunctionHostdevEnsurePCIAddresses(virDomainObjPtr vm,
+ virDomainDeviceDefListPtr devlist,
+ virQEMUDriverPtr driver)
+{
+ int ret = -1, aggrslotidx = 0;
+ virBitmapPtr slotmap = NULL;
+ size_t i;
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ virDomainPCIMultifunctionAddressInfoPtr devinfos = NULL;
+
+ if (devlist->count > VIR_PCI_MAX_FUNCTIONS) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("More devices per slot found"));
+ return -1;
+ }
+
+ for (i = 0; i < devlist->count; i++)
+ qemuDomainFillDevicePCIConnectFlags(vm->def, devlist->devs[i],
+ priv->qemuCaps, driver);
+
+ if (VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs +
devlist->count) < 0)
+ return -1;
+
+ /* Temporarily add the devices to the domain def to get the
+ * next aggregateIdx */
+ for (i = 0; i < devlist->count; i++)
+ vm->def->hostdevs[vm->def->nhostdevs++] =
devlist->devs[i]->data.hostdev;
+
+ for (i = 0; i < devlist->count; i++) {
+ virDomainHostdevDefPtr hostdev = devlist->devs[i]->data.hostdev;
+
+ if (qemuDomainIsPSeries(vm->def))
+ /* Isolation groups are only relevant for pSeries guests */
+ qemuDomainFillDeviceIsolationGroup(vm->def, devlist->devs[i]);
+
+ qemuDomainSetDeviceSlotAggregateIdx(vm->def, devlist->devs[i]);
+ aggrslotidx = aggrslotidx ? aggrslotidx : hostdev->info->aggregateSlotIdx;
+
+ if (aggrslotidx != hostdev->info->aggregateSlotIdx) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Devices belong to different PCI slots"));
+ return -1;
+ }
+ }
+
+ for (i = 0; i < devlist->count; i++)
+ vm->def->hostdevs[--(vm->def->nhostdevs)] = NULL;
+
+ slotmap = virDomainDefHostdevGetPCIOnlineFunctionMap(vm->def, aggrslotidx);
+ if (!virBitmapIsAllClear(slotmap)) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("Device already assigned to guest"));
+ return -1;
+ }
+
+ if (VIR_ALLOC(devinfos) < 0)
+ return -1;
+
+ for (i = 0; i < devlist->count; i++) {
+ virDomainHostdevDefPtr hostdev = devlist->devs[i]->data.hostdev;
+ virPCIDeviceAddress addr = hostdev->source.subsys.u.pci.addr;
+
+ devinfos->infos[addr.function] = hostdev->info;
+ }
+
+ ret = virDomainPCIAddressEnsureMultifunctionAddress(priv->pciaddrs, devinfos);
+ VIR_FREE(devinfos);
+ return ret;
+}
+
+
void
qemuDomainReleaseDeviceAddress(virDomainObjPtr vm,
virDomainDeviceInfoPtr info)
diff --git a/src/qemu/qemu_domain_address.h b/src/qemu/qemu_domain_address.h
index 8d51456f5b..b9ba577001 100644
--- a/src/qemu/qemu_domain_address.h
+++ b/src/qemu/qemu_domain_address.h
@@ -50,6 +50,10 @@ int qemuDomainEnsurePCIAddress(virDomainObjPtr obj,
virQEMUDriverPtr driver)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+int qemuDomainPCIMultifunctionHostdevEnsurePCIAddresses(virDomainObjPtr obj,
+ virDomainDeviceDefListPtr
devlist,
+ virQEMUDriverPtr driver);
+
void qemuDomainFillDeviceIsolationGroup(virDomainDefPtr def,
virDomainDeviceDefPtr dev)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index f880a5d6cf..3217d93a73 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -7977,17 +7977,28 @@ qemuDomainAttachDeviceLiveInternal(virDomainObjPtr vm,
static int
qemuDomainAttachDeviceLive(virDomainObjPtr vm,
- virDomainDeviceDefPtr dev,
+ virDomainDeviceDefListPtr devlist,
virQEMUDriverPtr driver)
{
int ret = -1;
+ size_t i;
- if (virDomainDefCompatibleDevice(vm->def, dev, NULL,
- VIR_DOMAIN_DEVICE_ACTION_ATTACH,
- false) < 0)
- return -1;
+ for (i = 0; i < devlist->count; i++)
+ if (virDomainDefCompatibleDevice(vm->def, devlist->devs[i], NULL,
+ VIR_DOMAIN_DEVICE_ACTION_ATTACH,
+ false) < 0)
+ return ret;
+
+ if (devlist->count > 1) {
+ ret = qemuDomainAttachMultifunctionDevice(vm, devlist, driver);
+ if (ret == 0) {
+ for (i = 0; i < devlist->count; i++)
+ devlist->devs[i]->data.hostdev = NULL;
+ }
+ } else if (devlist->count == 1) {
+ ret = qemuDomainAttachDeviceLiveInternal(vm, devlist->devs[0], driver);
+ }
- ret = qemuDomainAttachDeviceLiveInternal(vm, dev, driver);
if (ret == 0)
ret = qemuDomainUpdateDeviceList(driver, vm, QEMU_ASYNC_JOB_NONE);
@@ -8334,18 +8345,24 @@ qemuDomainAttachDeviceConfigInternal(virDomainDefPtr vmdef,
static int
qemuDomainAttachDeviceConfig(virDomainDefPtr vmdef,
- virDomainDeviceDefPtr dev,
+ virDomainDeviceDefListPtr devlist,
virQEMUCapsPtr qemuCaps,
unsigned int parse_flags,
virDomainXMLOptionPtr xmlopt)
{
- if (virDomainDefCompatibleDevice(vmdef, dev, NULL,
- VIR_DOMAIN_DEVICE_ACTION_ATTACH,
- false) < 0)
- return -1;
+ size_t i;
- if (qemuDomainAttachDeviceConfigInternal(vmdef, dev))
- return -1;
+ for (i = 0; i < devlist->count; i++) {
+ if (virDomainDefCompatibleDevice(vmdef, devlist->devs[i], NULL,
+ VIR_DOMAIN_DEVICE_ACTION_ATTACH,
+ false) < 0)
+ return -1;
+ }
+
+ for (i = 0; i < devlist->count; i++) {
+ if (qemuDomainAttachDeviceConfigInternal(vmdef, devlist->devs[i]))
+ return -1;
+ }
if (virDomainDefPostParse(vmdef, parse_flags, xmlopt, qemuCaps) < 0)
return -1;
@@ -8711,9 +8728,14 @@ qemuDomainAttachDeviceLiveAndConfig(virDomainObjPtr vm,
qemuDomainObjPrivatePtr priv = vm->privateData;
virDomainDefPtr vmdef = NULL;
g_autoptr(virQEMUDriverConfig) cfg = NULL;
- virDomainDeviceDefPtr devConf = NULL;
- virDomainDeviceDef devConfSave = { 0 };
+ virDomainDeviceDefPtr devConfSave = NULL;
virDomainDeviceDefPtr devLive = NULL;
+ virDomainDeviceDefListPtr devListConf = NULL;
+ virDomainDeviceDefListPtr devListConfSave = NULL;
+ virDomainDeviceDefListPtr devListLive = NULL;
+ virDomainDeviceDefListData data = {.def = vm->def,
+ .xmlopt = driver->xmlopt};
+ size_t i;
int ret = -1;
unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
VIR_DOMAIN_DEF_PARSE_ABI_UPDATE;
@@ -8732,52 +8754,58 @@ qemuDomainAttachDeviceLiveAndConfig(virDomainObjPtr vm,
if (!vmdef)
goto cleanup;
- if (!(devConf = virDomainDeviceDefParse(xml, vmdef,
- driver->xmlopt, priv->qemuCaps,
- parse_flags)))
+ devListConf = qemuDomainDeviceParseXMLMany(xml, &data, priv->qemuCaps,
+ parse_flags);
+ if (!devListConf)
goto cleanup;
/*
- * devConf will be NULLed out by
+ * devListConf will be NULLed out by
* qemuDomainAttachDeviceConfig(), so save it for later use by
* qemuDomainAttachDeviceLiveAndConfigHomogenize()
*/
- devConfSave = *devConf;
-
- if (virDomainDeviceValidateAliasForHotplug(vm, devConf,
- VIR_DOMAIN_AFFECT_CONFIG) < 0)
- goto cleanup;
+ if (flags & VIR_DOMAIN_AFFECT_LIVE) {
+ devListConfSave = virDomainDeviceDefListCopy(devListConf,
+ &data,
+ priv->qemuCaps);
+ if (!devListConfSave)
+ goto cleanup;
+ }
- if (virDomainDefCompatibleDevice(vmdef, devConf, NULL,
- VIR_DOMAIN_DEVICE_ACTION_ATTACH,
- false) < 0)
- goto cleanup;
+ for (i = 0; i < devListConf->count; i++) {
+ if (virDomainDeviceValidateAliasForHotplug(vm,
+ devListConf->devs[i],
+ flags) < 0)
+ goto cleanup;
+ }
- if (qemuDomainAttachDeviceConfig(vmdef, devConf, priv->qemuCaps,
+ if (qemuDomainAttachDeviceConfig(vmdef, devListConf, priv->qemuCaps,
parse_flags,
driver->xmlopt) < 0)
goto cleanup;
}
if (flags & VIR_DOMAIN_AFFECT_LIVE) {
- if (!(devLive = virDomainDeviceDefParse(xml, vm->def,
- driver->xmlopt, priv->qemuCaps,
- parse_flags)))
+ devListLive = qemuDomainDeviceParseXMLMany(xml, &data, priv->qemuCaps,
+ parse_flags);
+ if (!devListLive)
goto cleanup;
- if (flags & VIR_DOMAIN_AFFECT_CONFIG)
- qemuDomainAttachDeviceLiveAndConfigHomogenize(&devConfSave, devLive);
+ for (i = 0; i < devListLive->count; i++) {
+ devLive = devListLive->devs[i];
- if (virDomainDeviceValidateAliasForHotplug(vm, devLive,
- VIR_DOMAIN_AFFECT_LIVE) < 0)
- goto cleanup;
+ if (flags & VIR_DOMAIN_AFFECT_CONFIG) {
+ devConfSave = devListConfSave->devs[i];
+ qemuDomainAttachDeviceLiveAndConfigHomogenize(devConfSave,
+ devLive);
+ }
- if (virDomainDefCompatibleDevice(vm->def, devLive, NULL,
- VIR_DOMAIN_DEVICE_ACTION_ATTACH,
- true) < 0)
- goto cleanup;
+ if (virDomainDeviceValidateAliasForHotplug(vm, devLive,
+ VIR_DOMAIN_AFFECT_LIVE) < 0)
+ goto cleanup;
+ }
- if (qemuDomainAttachDeviceLive(vm, devLive, driver) < 0)
+ if (qemuDomainAttachDeviceLive(vm, devListLive, driver) < 0)
goto cleanup;
/*
* update domain status forcibly because the domain status may be
@@ -8800,8 +8828,11 @@ qemuDomainAttachDeviceLiveAndConfig(virDomainObjPtr vm,
ret = 0;
cleanup:
virDomainDefFree(vmdef);
- virDomainDeviceDefFree(devConf);
- virDomainDeviceDefFree(devLive);
+ devConfSave = NULL;
+ devLive = NULL;
+ virDomainDeviceDefListFree(devListConf);
+ virDomainDeviceDefListFree(devListConfSave);
+ virDomainDeviceDefListFree(devListLive);
return ret;
}
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index b9946e8b8e..a6c520ec1b 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -1685,6 +1685,92 @@ qemuDomainAttachHostPCIDevice(virQEMUDriverPtr driver,
}
+static int
+qemuiHostdevPCIMultifunctionDevicesListSort(const void *p1,
+ const void *p2)
+{
+ virDomainDeviceDefPtr a = *(virDomainDeviceDefPtr *) p1;
+ virDomainDeviceDefPtr b = *(virDomainDeviceDefPtr *) p2;
+ virPCIDeviceAddressPtr addr1 = &a->data.hostdev->source.subsys.u.pci.addr;
+ virPCIDeviceAddressPtr addr2 = &b->data.hostdev->source.subsys.u.pci.addr;
+
+ return addr1->function - addr2->function;
+}
+
+
+int
+qemuDomainAttachMultifunctionDevice(virDomainObjPtr vm,
+ virDomainDeviceDefListPtr devlist,
+ virQEMUDriverPtr driver)
+{
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ size_t i, d, h = devlist->count;
+ int ret = -1;
+ char *alias;
+ virObjectEventPtr event;
+ virDomainHostdevDefPtr hostdev;
+
+ qsort(devlist->devs, devlist->count, sizeof(*devlist->devs),
+ qemuiHostdevPCIMultifunctionDevicesListSort);
+
+ if (qemuDomainPCIMultifunctionHostdevEnsurePCIAddresses(vm, devlist,
+ driver) < 0)
+ return -1;
+
+ for (d = 0; d < devlist->count; d++) {
+ hostdev = devlist->devs[d]->data.hostdev;
+
+ if (qemuDomainAttachPCIHostDevicePrepare(driver, vm->def, hostdev,
+ priv->qemuCaps) < 0)
+ goto cleanup;
+ }
+
+ /* Hotplug all functions, and Primary at last */
+ for (h = devlist->count; h > 0; h--) {
+ /* The functions need not be contiguous, as a card may be sold with
+ * minimal functionality and then install the additional functions on
+ * purchase into any of the daughter-card connectors.
+ */
+ hostdev = devlist->devs[h-1]->data.hostdev;
+
+ ret = qemuDomainAttachHostPCIDevice(driver, vm, hostdev);
+ if (ret)
+ goto release;
+
+ alias = hostdev->info->alias;
+ hostdev = NULL;
+
+ event = virDomainEventDeviceAddedNewFromObj(vm, alias);
+ virObjectEventStateQueue(driver->domainEventState, event);
+ }
+
+ release:
+ /* Release addresses for the device which are not hotplugged.
+ */
+ for (i = 0; i < h; i++)
+ qemuDomainReleaseDeviceAddress(vm,
devlist->devs[i]->data.hostdev->info);
+
+ cleanup:
+ /* If none are actually hotplugged and just detached from the
+ * host driver reattach the devices to host driver.
+ *
+ * If one of the hotplug failed, those which are already hotplugged cannot
+ * be unplugged as they are released by qemu only on guest reboot even
+ * if we issue device_del on them.
+ * So, dont attempt to reattach any of them.
+ * NB: Let them be in the guest as they are not used anyway without
+ * function-zero?
+ */
+ if (d > 0 && h == devlist->count) {
+ for (i = 0; i < d; i++)
+ qemuHostdevReAttachPCIDevices(driver, vm->def->name,
+ &devlist->devs[i]->data.hostdev, 1);
+ }
+
+ return ret;
+}
+
+
void
qemuDomainDelTLSObjects(virQEMUDriverPtr driver,
virDomainObjPtr vm,
diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h
index 9df4a4e5f7..b62528a6d9 100644
--- a/src/qemu/qemu_hotplug.h
+++ b/src/qemu/qemu_hotplug.h
@@ -126,6 +126,10 @@ int qemuDomainAttachPCIHostDevicePrepare(virQEMUDriverPtr driver,
virDomainDefPtr def,
virDomainHostdevDefPtr dev,
virQEMUCapsPtr qemuCaps);
+int
+qemuDomainAttachMultifunctionDevice(virDomainObjPtr vm,
+ virDomainDeviceDefListPtr devlist,
+ virQEMUDriverPtr driver);
int
qemuDomainChrInsert(virDomainDefPtr vmdef,
diff --git a/tests/qemuhotplugtest.c b/tests/qemuhotplugtest.c
index 57f64d191c..32d186ef73 100644
--- a/tests/qemuhotplugtest.c
+++ b/tests/qemuhotplugtest.c
@@ -90,6 +90,7 @@ qemuHotplugCreateObjects(virDomainXMLOptionPtr xmlopt,
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_PR_MANAGER_HELPER);
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SCSI_BLOCK);
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_VIRTIO_PCI_DISABLE_LEGACY);
+ virQEMUCapsSet(priv->qemuCaps, X_QEMU_CAPS_PCI_MULTIFUNCTION);
if (qemuTestCapsCacheInsert(driver.qemuCapsCache, priv->qemuCaps) < 0)
return -1;
@@ -118,9 +119,14 @@ qemuHotplugCreateObjects(virDomainXMLOptionPtr xmlopt,
static int
testQemuHotplugAttach(virDomainObjPtr vm,
- virDomainDeviceDefPtr dev)
+ virDomainDeviceDefListPtr devlist)
{
int ret = -1;
+ virDomainDeviceDefPtr dev;
+
+ if (devlist->count > 1)
+ return qemuDomainAttachMultifunctionDevice(vm, devlist, &driver);
+ dev = devlist->devs[0];
switch (dev->type) {
case VIR_DOMAIN_DEVICE_DISK:
@@ -245,7 +251,9 @@ testQemuHotplug(const void *data)
bool keep = test->keep;
unsigned int device_parse_flags = 0;
virDomainObjPtr vm = NULL;
- virDomainDeviceDefPtr dev = NULL;
+ virDomainDeviceDefPtr dev = NULL; /* temporary */
+ virDomainDeviceDefListPtr devlist = NULL;
+ virDomainDeviceDefListData listdata;
virCapsPtr caps = NULL;
qemuMonitorTestPtr test_mon = NULL;
qemuDomainObjPrivatePtr priv = NULL;
@@ -283,11 +291,15 @@ testQemuHotplug(const void *data)
if (test->action == ATTACH)
device_parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE;
- if (!(dev = virDomainDeviceDefParse(device_xml, vm->def,
- driver.xmlopt, NULL,
- device_parse_flags)))
+ listdata.def = vm->def;
+ listdata.xmlopt = driver.xmlopt;
+ devlist = qemuDomainDeviceParseXMLMany(device_xml, &listdata, NULL,
+ device_parse_flags);
+ if (!devlist)
goto cleanup;
+ dev = devlist->devs[0]; /* temporary */
+
/* Now is the best time to feed the spoofed monitor with predefined
* replies. */
if (!(test_mon = qemuMonitorTestNew(driver.xmlopt, vm, &driver,
@@ -316,11 +328,11 @@ testQemuHotplug(const void *data)
switch (test->action) {
case ATTACH:
- ret = testQemuHotplugAttach(vm, dev);
+ ret = testQemuHotplugAttach(vm, devlist);
if (ret == 0) {
/* vm->def stolen dev->data.* so we just need to free the dev
* envelope */
- VIR_FREE(dev);
+ virDomainDeviceDefListFreeShallow(devlist);
}
if (ret == 0 || fail)
ret = testQemuHotplugCheckResult(vm, result_xml,
@@ -356,7 +368,7 @@ testQemuHotplug(const void *data)
virObjectUnref(vm);
test->vm = NULL;
}
- virDomainDeviceDefFree(dev);
+ virDomainDeviceDefListFree(devlist);
virObjectUnref(caps);
qemuMonitorTestFree(test_mon);
return ((ret < 0 && fail) || (!ret && !fail)) ? 0 : -1;
@@ -840,6 +852,17 @@ mymain(void)
"device_add", QMP_OK);
DO_TEST_DETACH("pseries-base-live", "hostdev-pci", false, false,
"device_del", QMP_DEVICE_DELETED("hostdev0")
QMP_OK);
+ DO_TEST_ATTACH("base-live", "multifunction-hostdev-pci", false,
false,
+ "device_add", QMP_OK,
+ "device_add", QMP_OK,
+ "device_add", QMP_OK,
+ "device_add", QMP_OK);
+
+ qemuTestSetHostArch(&driver, VIR_ARCH_PPC64);
+ DO_TEST_ATTACH("pseries-base-live",
"multifunction-hostdev-pci-2", false, false,
+ "device_add", QMP_OK,
+ "device_add", QMP_OK);
+ qemuTestSetHostArch(&driver, VIR_ARCH_X86_64);
DO_TEST_ATTACH("base-live", "watchdog", false, true,
"watchdog-set-action", QMP_OK,
diff --git a/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci-2.xml
b/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci-2.xml
new file mode 100644
index 0000000000..02e2236e5d
--- /dev/null
+++ b/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci-2.xml
@@ -0,0 +1,14 @@
+<devices>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0001' bus='0x01' slot='0x00'
function='0x1'/>
+ </source>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0001' bus='0x01' slot='0x00'
function='0x0'/>
+ </source>
+ </hostdev>
+</devices>
diff --git a/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci.xml
b/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci.xml
new file mode 100644
index 0000000000..bef4be219f
--- /dev/null
+++ b/tests/qemuhotplugtestdevices/qemuhotplug-multifunction-hostdev-pci.xml
@@ -0,0 +1,26 @@
+<devices>
+ <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='0x2'/>
+ </source>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x3'/>
+ </source>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x0'/>
+ </source>
+ </hostdev>
+</devices>
diff --git
a/tests/qemuhotplugtestdomains/qemuhotplug-base-live+multifunction-hostdev-pci.xml
b/tests/qemuhotplugtestdomains/qemuhotplug-base-live+multifunction-hostdev-pci.xml
new file mode 100644
index 0000000000..9dd04d3350
--- /dev/null
+++ b/tests/qemuhotplugtestdomains/qemuhotplug-base-live+multifunction-hostdev-pci.xml
@@ -0,0 +1,82 @@
+<domain type='kvm' id='7'>
+ <name>hotplug</name>
+ <uuid>d091ea82-29e6-2e34-3005-f02617b36e87</uuid>
+ <memory unit='KiB'>4194304</memory>
+ <currentMemory unit='KiB'>4194304</currentMemory>
+ <vcpu placement='static'>4</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <features>
+ <acpi/>
+ <apic/>
+ <pae/>
+ </features>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>restart</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <controller type='usb' index='0'>
+ <alias name='usb'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='ide' index='0'>
+ <alias name='ide'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x1'/>
+ </controller>
+ <controller type='scsi' index='0' model='virtio-scsi'>
+ <alias name='scsi0'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'>
+ <alias name='pci'/>
+ </controller>
+ <controller type='virtio-serial' index='0'>
+ <alias name='virtio-serial0'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x0'/>
+ </controller>
+ <input type='mouse' bus='ps2'>
+ <alias name='input0'/>
+ </input>
+ <input type='keyboard' bus='ps2'>
+ <alias name='input1'/>
+ </input>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x3'/>
+ </source>
+ <alias name='hostdev0'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x3'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x2'/>
+ </source>
+ <alias name='hostdev1'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x2'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x1'/>
+ </source>
+ <alias name='hostdev2'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x1'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x0'/>
+ </source>
+ <alias name='hostdev3'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x0' multifunction='on'/>
+ </hostdev>
+ <memballoon model='none'/>
+ </devices>
+ <seclabel type='none' model='none'/>
+</domain>
diff --git
a/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci-2.xml
b/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci-2.xml
new file mode 100644
index 0000000000..f99c1b3741
--- /dev/null
+++
b/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci-2.xml
@@ -0,0 +1,59 @@
+<domain type='kvm' id='7'>
+ <name>hotplug</name>
+ <uuid>d091ea82-29e6-2e34-3005-f02617b36e87</uuid>
+ <memory unit='KiB'>4194304</memory>
+ <currentMemory unit='KiB'>4194304</currentMemory>
+ <vcpu placement='static'>4</vcpu>
+ <os>
+ <type arch='ppc64' machine='pseries'>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-ppc64</emulator>
+ <controller type='pci' index='0' model='pci-root'>
+ <model name='spapr-pci-host-bridge'/>
+ <target index='0'/>
+ <alias name='pci.0'/>
+ </controller>
+ <controller type='pci' index='1' model='pci-root'>
+ <model name='spapr-pci-host-bridge'/>
+ <target index='1'/>
+ <alias name='pci.1'/>
+ </controller>
+ <controller type='usb' index='0'>
+ <alias name='usb'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x0'/>
+ </controller>
+ <input type='keyboard' bus='usb'>
+ <alias name='input0'/>
+ <address type='usb' bus='0' port='1'/>
+ </input>
+ <input type='mouse' bus='usb'>
+ <alias name='input1'/>
+ <address type='usb' bus='0' port='2'/>
+ </input>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0001' bus='0x01' slot='0x00'
function='0x1'/>
+ </source>
+ <alias name='hostdev0'/>
+ <address type='pci' domain='0x0000' bus='0x01'
slot='0x01' function='0x1'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0001' bus='0x01' slot='0x00'
function='0x0'/>
+ </source>
+ <alias name='hostdev1'/>
+ <address type='pci' domain='0x0000' bus='0x01'
slot='0x01' function='0x0' multifunction='on'/>
+ </hostdev>
+ <memballoon model='none'/>
+ <panic model='pseries'/>
+ </devices>
+ <seclabel type='none' model='none'/>
+</domain>
diff --git
a/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci.xml
b/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci.xml
new file mode 100644
index 0000000000..9338d42e2e
--- /dev/null
+++
b/tests/qemuhotplugtestdomains/qemuhotplug-pseries-base-live+multifunction-hostdev-pci.xml
@@ -0,0 +1,69 @@
+<domain type='kvm' id='7'>
+ <name>hotplug</name>
+ <uuid>d091ea82-29e6-2e34-3005-f02617b36e87</uuid>
+ <memory unit='KiB'>4194304</memory>
+ <currentMemory unit='KiB'>4194304</currentMemory>
+ <vcpu placement='static'>4</vcpu>
+ <os>
+ <type arch='ppc64' machine='pseries'>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-ppc64</emulator>
+ <controller type='pci' index='0' model='pci-root'>
+ <model name='spapr-pci-host-bridge'/>
+ <target index='0'/>
+ <alias name='pci.0'/>
+ </controller>
+ <controller type='pci' index='1' model='pci-root'>
+ <model name='spapr-pci-host-bridge'/>
+ <target index='1'/>
+ <alias name='pci.1'/>
+ </controller>
+ <controller type='usb' index='0'>
+ <alias name='usb'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x0'/>
+ </controller>
+ <input type='keyboard' bus='usb'>
+ <alias name='input0'/>
+ <address type='usb' bus='0' port='1'/>
+ </input>
+ <input type='mouse' bus='usb'>
+ <alias name='input1'/>
+ <address type='usb' bus='0' port='2'/>
+ </input>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x2'/>
+ </source>
+ <alias name='hostdev0'/>
+ <address type='pci' domain='0x0000' bus='0x01'
slot='0x01' function='0x2'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x1'/>
+ </source>
+ <alias name='hostdev1'/>
+ <address type='pci' domain='0x0000' bus='0x01'
slot='0x01' function='0x1'/>
+ </hostdev>
+ <hostdev mode='subsystem' type='pci' managed='yes'>
+ <driver name='vfio'/>
+ <source>
+ <address domain='0x0005' bus='0x90' slot='0x01'
function='0x0'/>
+ </source>
+ <alias name='hostdev2'/>
+ <address type='pci' domain='0x0000' bus='0x01'
slot='0x01' function='0x0' multifunction='on'/>
+ </hostdev>
+ <memballoon model='none'>
+ <alias name='balloon0'/>
+ </memballoon>
+ <panic model='pseries'/>
+ </devices>
+ <seclabel type='none' model='none'/>
+</domain>
--
2.26.2