From c034c1a3b29050b80a50bb8227a030454eea4a54 Mon Sep 17 00:00:00 2001 From: Mark McLoughlin Date: Aug 19 2009 16:26:27 +0000 Subject: * Wed Aug 19 2009 Mark McLoughlin - 0.7.0-5 - Add PCI host device hotplug support - Allow PCI bus reset to reset other devices (#499678) - Fix stupid PCI reset error message (bug #499678) - Allow PM reset on multi-function PCI devices (bug #515689) - Re-attach PCI host devices after guest shuts down (bug #499561) - Fix list corruption after disk hot-unplug - Fix minor 'virsh nodedev-list --tree' annoyance --- diff --git a/libvirt-0.6.4-svirt-sound.patch b/libvirt-0.6.4-svirt-sound.patch index 68cbf4d..5c209b4 100644 --- a/libvirt-0.6.4-svirt-sound.patch +++ b/libvirt-0.6.4-svirt-sound.patch @@ -1,4 +1,4 @@ -From f9355301cf4dda452308b616e8c00e59be5aec0a Mon Sep 17 00:00:00 2001 +From 1e99b2edee0eb3ca1c600e5bd5c55741cb95a29a Mon Sep 17 00:00:00 2001 From: Daniel P. Berrange Date: Mon, 17 Aug 2009 08:32:08 +0100 Subject: [PATCH] Disable sound cards when running sVirt diff --git a/libvirt-add-pci-hostdev-hotplug-support.patch b/libvirt-add-pci-hostdev-hotplug-support.patch new file mode 100644 index 0000000..203d3eb --- /dev/null +++ b/libvirt-add-pci-hostdev-hotplug-support.patch @@ -0,0 +1,465 @@ +From 332979bb680d833529ab9cecac6828c6ce54d731 Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Fri, 14 Aug 2009 08:31:10 +0100 +Subject: [PATCH] Add host PCI device hotplug support + +Attaching a host PCI device to a qemu guest is done with a +straightforward 'pci_add pci_addr auto host host=XX:XX.X' command. + +Like with NIC and disk hotplug, we need to retain the guest PCI address +assigned by qemu so that we can use it for hot-unplug. + +Identifying a device for detach is done using the host PCI address. + +Managed mode is handled by detaching/resetting the device before +attaching it to the guest and re-attaching it after detaching it from +the guest. + +(cherry picked from commit 7636ef4630fc15c3d559eceb5b5c4fb1524b7c5a) +(cherry picked from commit 0c5b7b93a3cdb197c55d79c2605e9e19e3af43f5) +(cherry picked from commit 60ff07585ca8f7e639fed477e2e2cf79ce1c5c21) +(cherry picked from commit 4e12af5623e4a962a6bb911af06fa29aa85befba) +(cherry picked from commit 4dbecff9fbd5b5d1154bc7a41a5d4dd00533b359) +(cherry picked from commit 12edef9a6aca5bd9a2ea18b73ca862f615684d84) +(cherry picked from commit 457e05062863a35c7efb35470886b9b83a49d04d) +(cherry picked from commit e8ad33931296c67de0538e78d12e21706a826d37) + +Fedora-patch: libvirt-add-pci-hostdev-hotplug-support.patch +--- + src/domain_conf.c | 33 +++++- + src/domain_conf.h | 13 +++ + src/libvirt_private.syms | 2 + + src/qemu_driver.c | 266 ++++++++++++++++++++++++++++++++++++++++++++-- + 4 files changed, 300 insertions(+), 14 deletions(-) + +diff --git a/src/domain_conf.c b/src/domain_conf.c +index 2301a96..bad53f7 100644 +--- a/src/domain_conf.c ++++ b/src/domain_conf.c +@@ -1977,7 +1977,8 @@ out: + static int + virDomainHostdevSubsysPciDefParseXML(virConnectPtr conn, + const xmlNodePtr node, +- virDomainHostdevDefPtr def) { ++ virDomainHostdevDefPtr def, ++ int flags) { + + int ret = -1; + xmlNodePtr cur; +@@ -2049,6 +2050,20 @@ virDomainHostdevSubsysPciDefParseXML(virConnectPtr conn, + _("pci address needs function id")); + goto out; + } ++ } else if ((flags & VIR_DOMAIN_XML_INTERNAL_STATUS) && ++ xmlStrEqual(cur->name, BAD_CAST "state")) { ++ char *devaddr = virXMLPropString(cur, "devaddr"); ++ if (devaddr && ++ sscanf(devaddr, "%x:%x:%x", ++ &def->source.subsys.u.pci.guest_addr.domain, ++ &def->source.subsys.u.pci.guest_addr.bus, ++ &def->source.subsys.u.pci.guest_addr.slot) < 3) { ++ virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, ++ _("Unable to parse devaddr parameter '%s'"), ++ devaddr); ++ VIR_FREE(devaddr); ++ goto out; ++ } + } else { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown pci source type '%s'"), +@@ -2123,7 +2138,7 @@ virDomainHostdevDefParseXML(virConnectPtr conn, + } + if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && + def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) { +- if (virDomainHostdevSubsysPciDefParseXML(conn, cur, def) < 0) ++ if (virDomainHostdevSubsysPciDefParseXML(conn, cur, def, flags) < 0) + goto error; + } + } else { +@@ -3937,7 +3952,8 @@ virDomainGraphicsDefFormat(virConnectPtr conn, + static int + virDomainHostdevDefFormat(virConnectPtr conn, + virBufferPtr buf, +- virDomainHostdevDefPtr def) ++ virDomainHostdevDefPtr def, ++ int flags) + { + const char *mode = virDomainHostdevModeTypeToString(def->mode); + const char *type; +@@ -3978,6 +3994,15 @@ virDomainHostdevDefFormat(virConnectPtr conn, + def->source.subsys.u.pci.bus, + def->source.subsys.u.pci.slot, + def->source.subsys.u.pci.function); ++ if (flags & VIR_DOMAIN_XML_INTERNAL_STATUS) { ++ virBufferAddLit(buf, " source.subsys.u.pci.guest_addr.domain, ++ def->source.subsys.u.pci.guest_addr.bus, ++ def->source.subsys.u.pci.guest_addr.slot); ++ virBufferAddLit(buf, "/>\n"); ++ } + } + + virBufferAddLit(buf, " \n"); +@@ -4192,7 +4217,7 @@ char *virDomainDefFormat(virConnectPtr conn, + goto cleanup; + + for (n = 0 ; n < def->nhostdevs ; n++) +- if (virDomainHostdevDefFormat(conn, &buf, def->hostdevs[n]) < 0) ++ if (virDomainHostdevDefFormat(conn, &buf, def->hostdevs[n], flags) < 0) + goto cleanup; + + virBufferAddLit(&buf, " \n"); +diff --git a/src/domain_conf.h b/src/domain_conf.h +index 63fca76..44302be 100644 +--- a/src/domain_conf.h ++++ b/src/domain_conf.h +@@ -391,6 +391,11 @@ struct _virDomainHostdevDef { + unsigned bus; + unsigned slot; + unsigned function; ++ struct { ++ unsigned domain; ++ unsigned bus; ++ unsigned slot; ++ } guest_addr; + } pci; + } u; + } subsys; +@@ -404,6 +409,14 @@ struct _virDomainHostdevDef { + char* target; + }; + ++static inline int ++virHostdevHasValidGuestAddr(virDomainHostdevDefPtr def) ++{ ++ return def->source.subsys.u.pci.guest_addr.domain || ++ def->source.subsys.u.pci.guest_addr.bus || ++ def->source.subsys.u.pci.guest_addr.slot; ++} ++ + /* Flags for the 'type' field in next struct */ + enum virDomainDeviceType { + VIR_DOMAIN_DEVICE_DISK, +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index 4f1b01f..22131c4 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -88,6 +88,8 @@ virDomainGetRootFilesystem; + virDomainGraphicsTypeFromString; + virDomainGraphicsDefFree; + virDomainHostdevDefFree; ++virDomainHostdevModeTypeToString; ++virDomainHostdevSubsysTypeToString; + virDomainInputDefFree; + virDomainLifecycleTypeFromString; + virDomainLifecycleTypeToString; +diff --git a/src/qemu_driver.c b/src/qemu_driver.c +index cbc27c4..99dac52 100644 +--- a/src/qemu_driver.c ++++ b/src/qemu_driver.c +@@ -5258,9 +5258,91 @@ cleanup: + return -1; + } + +-static int qemudDomainAttachHostDevice(virConnectPtr conn, +- virDomainObjPtr vm, +- virDomainDeviceDefPtr dev) ++static int qemudDomainAttachHostPciDevice(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainObjPtr vm, ++ virDomainDeviceDefPtr dev) ++{ ++ virDomainHostdevDefPtr hostdev = dev->data.hostdev; ++ char *cmd, *reply; ++ unsigned domain, bus, slot; ++ pciDevice *pci; ++ ++ if (VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs+1) < 0) { ++ virReportOOMError(conn); ++ return -1; ++ } ++ ++ pci = pciGetDevice(conn, ++ hostdev->source.subsys.u.pci.domain, ++ hostdev->source.subsys.u.pci.bus, ++ hostdev->source.subsys.u.pci.slot, ++ hostdev->source.subsys.u.pci.function); ++ if (!dev) ++ return -1; ++ ++ if ((hostdev->managed && pciDettachDevice(conn, pci) < 0) || ++ pciResetDevice(conn, pci, driver->activePciHostdevs) < 0) { ++ pciFreeDevice(conn, pci); ++ return -1; ++ } ++ ++ if (pciDeviceListAdd(conn, driver->activePciHostdevs, pci) < 0) { ++ pciFreeDevice(conn, pci); ++ return -1; ++ } ++ ++ cmd = reply = NULL; ++ ++ if (virAsprintf(&cmd, "pci_add pci_addr=auto host host=%.2x:%.2x.%.1x", ++ hostdev->source.subsys.u.pci.bus, ++ hostdev->source.subsys.u.pci.slot, ++ hostdev->source.subsys.u.pci.function) < 0) { ++ virReportOOMError(conn); ++ goto error; ++ } ++ ++ if (qemudMonitorCommand(vm, cmd, &reply) < 0) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_OPERATION_FAILED, ++ "%s", _("cannot attach host pci device")); ++ goto error; ++ } ++ ++ if (strstr(reply, "invalid type: host")) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_NO_SUPPORT, "%s", ++ _("PCI device assignment is not supported by this version of qemu")); ++ goto error; ++ } ++ ++ if (qemudParsePciAddReply(vm, reply, &domain, &bus, &slot) < 0) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_OPERATION_FAILED, ++ _("parsing pci_add reply failed: %s"), reply); ++ goto error; ++ } ++ ++ hostdev->source.subsys.u.pci.guest_addr.domain = domain; ++ hostdev->source.subsys.u.pci.guest_addr.bus = bus; ++ hostdev->source.subsys.u.pci.guest_addr.slot = slot; ++ ++ vm->def->hostdevs[vm->def->nhostdevs++] = hostdev; ++ ++ VIR_FREE(reply); ++ VIR_FREE(cmd); ++ ++ return 0; ++ ++error: ++ pciDeviceListDel(conn, driver->activePciHostdevs, pci); ++ ++ VIR_FREE(reply); ++ VIR_FREE(cmd); ++ ++ return -1; ++} ++ ++static int qemudDomainAttachHostUsbDevice(virConnectPtr conn, ++ virDomainObjPtr vm, ++ virDomainDeviceDefPtr dev) + { + int ret; + char *cmd, *reply; +@@ -5310,6 +5392,36 @@ static int qemudDomainAttachHostDevice(virConnectPtr conn, + return 0; + } + ++static int qemudDomainAttachHostDevice(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainObjPtr vm, ++ virDomainDeviceDefPtr dev) ++{ ++ virDomainHostdevDefPtr hostdev = dev->data.hostdev; ++ ++ if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_NO_SUPPORT, ++ _("hostdev mode '%s' not supported"), ++ virDomainHostdevModeTypeToString(hostdev->mode)); ++ return -1; ++ } ++ ++ if (qemuDomainSetDeviceOwnership(conn, driver, dev, 0) < 0) ++ return -1; ++ ++ switch (hostdev->source.subsys.type) { ++ case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: ++ return qemudDomainAttachHostPciDevice(conn, driver, vm, dev); ++ case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: ++ return qemudDomainAttachHostUsbDevice(conn, vm, dev); ++ default: ++ qemudReportError(conn, dom, NULL, VIR_ERR_NO_SUPPORT, ++ _("hostdev subsys type '%s' not supported"), ++ virDomainHostdevSubsysTypeToString(hostdev->source.subsys.type)); ++ return -1; ++ } ++} ++ + static int qemudDomainAttachDevice(virDomainPtr dom, + const char *xml) { + struct qemud_driver *driver = dom->conn->privateData; +@@ -5411,13 +5523,8 @@ static int qemudDomainAttachDevice(virDomainPtr dom, + } + } else if (dev->type == VIR_DOMAIN_DEVICE_NET) { + ret = qemudDomainAttachNetDevice(dom->conn, driver, vm, dev, qemuCmdFlags); +- } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV && +- dev->data.hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && +- dev->data.hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { +- if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 0) < 0) +- goto cleanup; +- +- ret = qemudDomainAttachHostDevice(dom->conn, vm, dev); ++ } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { ++ ret = qemudDomainAttachHostDevice(dom->conn, driver, vm, dev); + } else { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, + _("device type '%s' cannot be attached"), +@@ -5630,6 +5737,143 @@ cleanup: + return ret; + } + ++static int qemudDomainDetachHostPciDevice(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainObjPtr vm, ++ virDomainDeviceDefPtr dev) ++{ ++ virDomainHostdevDefPtr detach; ++ char *cmd, *reply; ++ int i, ret; ++ pciDevice *pci; ++ ++ for (i = 0 ; i < vm->def->nhostdevs ; i++) { ++ unsigned domain = vm->def->hostdevs[i]->source.subsys.u.pci.domain; ++ unsigned bus = vm->def->hostdevs[i]->source.subsys.u.pci.bus; ++ unsigned slot = vm->def->hostdevs[i]->source.subsys.u.pci.slot; ++ unsigned function = vm->def->hostdevs[i]->source.subsys.u.pci.function; ++ ++ if (dev->data.hostdev->source.subsys.u.pci.domain == domain && ++ dev->data.hostdev->source.subsys.u.pci.bus == bus && ++ dev->data.hostdev->source.subsys.u.pci.slot == slot && ++ dev->data.hostdev->source.subsys.u.pci.function == function) { ++ detach = vm->def->hostdevs[i]; ++ break; ++ } ++ } ++ ++ if (!detach) { ++ qemudReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, ++ _("host pci device %.4x:%.2x:%.2x.%.1x not found"), ++ dev->data.hostdev->source.subsys.u.pci.domain, ++ dev->data.hostdev->source.subsys.u.pci.bus, ++ dev->data.hostdev->source.subsys.u.pci.slot, ++ dev->data.hostdev->source.subsys.u.pci.function); ++ return -1; ++ } ++ ++ if (!virHostdevHasValidGuestAddr(detach)) { ++ qemudReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, ++ "%s", _("hostdev cannot be detached - device state missing")); ++ return -1; ++ } ++ ++ if (virAsprintf(&cmd, "pci_del pci_addr=%.4x:%.2x:%.2x", ++ detach->source.subsys.u.pci.guest_addr.domain, ++ detach->source.subsys.u.pci.guest_addr.bus, ++ detach->source.subsys.u.pci.guest_addr.slot) < 0) { ++ virReportOOMError(conn); ++ return -1; ++ } ++ ++ if (qemudMonitorCommand(vm, cmd, &reply) < 0) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_OPERATION_FAILED, ++ "%s", _("cannot detach host pci device")); ++ VIR_FREE(cmd); ++ return -1; ++ } ++ ++ DEBUG("%s: pci_del reply: %s", vm->def->name, reply); ++ ++ /* If the command fails due to a wrong PCI address qemu prints ++ * 'invalid pci address'; nothing is printed on success */ ++ if (strstr(reply, "Invalid pci address")) { ++ qemudReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, ++ _("failed to detach host pci device: invalid PCI address %.4x:%.2x:%.2x: %s"), ++ detach->source.subsys.u.pci.guest_addr.domain, ++ detach->source.subsys.u.pci.guest_addr.bus, ++ detach->source.subsys.u.pci.guest_addr.slot, ++ reply); ++ VIR_FREE(reply); ++ VIR_FREE(cmd); ++ return -1; ++ } ++ ++ VIR_FREE(reply); ++ VIR_FREE(cmd); ++ ++ ret = 0; ++ ++ pci = pciGetDevice(conn, ++ detach->source.subsys.u.pci.domain, ++ detach->source.subsys.u.pci.bus, ++ detach->source.subsys.u.pci.slot, ++ detach->source.subsys.u.pci.function); ++ if (!pci) ++ ret = -1; ++ else { ++ pciDeviceListDel(conn, driver->activePciHostdevs, pci); ++ if (pciResetDevice(conn, pci, driver->activePciHostdevs) < 0) ++ ret = -1; ++ if (detach->managed && pciReAttachDevice(conn, pci) < 0) ++ ret = -1; ++ pciFreeDevice(conn, pci); ++ } ++ ++ if (i != --vm->def->nhostdevs) ++ memmove(&vm->def->hostdevs[i], ++ &vm->def->hostdevs[i+1], ++ sizeof(*vm->def->hostdevs) * (vm->def->nhostdevs-i)); ++ if (VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs) < 0) { ++ virReportOOMError(conn); ++ ret = -1; ++ } ++ ++ return ret; ++} ++ ++static int qemudDomainDetachHostDevice(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainObjPtr vm, ++ virDomainDeviceDefPtr dev) ++{ ++ virDomainHostdevDefPtr hostdev = dev->data.hostdev; ++ int ret; ++ ++ if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) { ++ qemudReportError(conn, dom, NULL, VIR_ERR_NO_SUPPORT, ++ _("hostdev mode '%s' not supported"), ++ virDomainHostdevModeTypeToString(hostdev->mode)); ++ return -1; ++ } ++ ++ switch (hostdev->source.subsys.type) { ++ case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: ++ ret = qemudDomainDetachHostPciDevice(conn, driver, vm, dev); ++ break; ++ default: ++ qemudReportError(conn, dom, NULL, VIR_ERR_NO_SUPPORT, ++ _("hostdev subsys type '%s' not supported"), ++ virDomainHostdevSubsysTypeToString(hostdev->source.subsys.type)); ++ return -1; ++ } ++ ++ if (qemuDomainSetDeviceOwnership(conn, driver, dev, 1) < 0) ++ VIR_WARN0("Fail to restore disk device ownership"); ++ ++ return ret; ++} ++ + static int qemudDomainDetachDevice(virDomainPtr dom, + const char *xml) { + struct qemud_driver *driver = dom->conn->privateData; +@@ -5670,6 +5914,8 @@ static int qemudDomainDetachDevice(virDomainPtr dom, + VIR_WARN0("Fail to restore disk device ownership"); + } else if (dev->type == VIR_DOMAIN_DEVICE_NET) { + ret = qemudDomainDetachNetDevice(dom->conn, vm, dev); ++ } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { ++ ret = qemudDomainDetachHostDevice(dom->conn, driver, vm, dev); + } else + qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("only SCSI or virtio disk device can be detached dynamically")); +-- +1.6.2.5 + diff --git a/libvirt-add-space-to-nodedev-list-tree.patch b/libvirt-add-space-to-nodedev-list-tree.patch new file mode 100644 index 0000000..fe88198 --- /dev/null +++ b/libvirt-add-space-to-nodedev-list-tree.patch @@ -0,0 +1,43 @@ +From 100eb35a80932cd9a162c38ecd2ab8b01894fb61 Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Mon, 17 Aug 2009 15:05:22 +0100 +Subject: [PATCH] Cosmetic change to 'virsh nodedev-list --tree' output + +Maybe it's just me, but I try to select an item from the tree using +double-click and get annoyed when "+-" gets included in the selection. + +* src/virsh.c: add a space between "+-" and the node device name + in 'virsh nodedev-list --tree' + +(cherry picked from commit 097c818bf00b3777778ffc32fea3a6ed1e741e2b) + +Fedora-patch: libvirt-add-space-to-nodedev-list-tree.patch +--- + src/virsh.c | 4 +++- + 1 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/src/virsh.c b/src/virsh.c +index 94c3c4e..2d0cf81 100644 +--- a/src/virsh.c ++++ b/src/virsh.c +@@ -5370,6 +5370,8 @@ cmdNodeListDevicesPrint(vshControl *ctl, + if (depth && depth < MAX_DEPTH) { + indentBuf[indentIdx] = '+'; + indentBuf[indentIdx+1] = '-'; ++ indentBuf[indentIdx+2] = ' '; ++ indentBuf[indentIdx+3] = '\0'; + } + + /* Print this device */ +@@ -5398,7 +5400,7 @@ cmdNodeListDevicesPrint(vshControl *ctl, + /* If there is a child device, then print another blank line */ + if (nextlastdev != -1) { + vshPrint(ctl, "%s", indentBuf); +- vshPrint(ctl, " |\n"); ++ vshPrint(ctl, " |\n"); + } + + /* Finally print all children */ +-- +1.6.2.5 + diff --git a/libvirt-allow-pci-hostdev-reset-to-reset-other-devices.patch b/libvirt-allow-pci-hostdev-reset-to-reset-other-devices.patch new file mode 100644 index 0000000..1e633b9 --- /dev/null +++ b/libvirt-allow-pci-hostdev-reset-to-reset-other-devices.patch @@ -0,0 +1,812 @@ +From 89eefbd116ae74c3a5cfcfc74a31a40b83c726c3 Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Mon, 17 Aug 2009 15:05:23 +0100 +Subject: [PATCH] Maintain a list of active PCI hostdevs and use it in pciResetDevice() + +https://bugzilla.redhat.com/499678 + +First we add a pciDeviceList type and add a qemuGetPciHostDeviceList() +function to build a list from a domain definition. Use this in +prepare/re-attach to simplify things and eliminate the multiple +pciGetDevice() calls. + +Then, as we start/shutdown guests we can add or delete devices as +appropriate from a list of active devices. + +Finally, in pciReset(), we can use this to determine whether its safe to +reset a device as a side effect of resetting another device. + +(cherry picked from commit 78675b228b76a83f83d64856bfb63b9e14c103a0) +(cherry picked from commit e8ad33931296c67de0538e78d12e21706a826d37) + +Fedora-patch: libvirt-allow-pci-hostdev-reset-to-reset-other-devices.patch +--- + src/libvirt_private.syms | 7 +- + src/pci.c | 211 +++++++++++++++++++++++++++++++++-------- + src/pci.h | 23 +++++- + src/qemu_conf.h | 3 + + src/qemu_driver.c | 237 +++++++++++++++++++++++++++------------------- + src/xen_unified.c | 2 +- + 6 files changed, 339 insertions(+), 144 deletions(-) + +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index bd63692..4f1b01f 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -278,7 +278,12 @@ pciFreeDevice; + pciDettachDevice; + pciReAttachDevice; + pciResetDevice; +- ++pciDeviceSetManaged; ++pciDeviceGetManaged; ++pciDeviceListNew; ++pciDeviceListFree; ++pciDeviceListAdd; ++pciDeviceListDel; + + # qparams.h + qparam_get_query; +diff --git a/src/pci.c b/src/pci.c +index 74f7ef0..96e5d6d 100644 +--- a/src/pci.c ++++ b/src/pci.c +@@ -63,6 +63,7 @@ struct _pciDevice { + unsigned pci_pm_cap_pos; + unsigned has_flr : 1; + unsigned has_pm_reset : 1; ++ unsigned managed : 1; + }; + + /* For virReportOOMError() and virReportSystemError() */ +@@ -225,7 +226,7 @@ pciWrite32(pciDevice *dev, unsigned pos, uint32_t val) + pciWrite(dev, pos, &buf[0], sizeof(buf)); + } + +-typedef int (*pciIterPredicate)(pciDevice *, pciDevice *); ++typedef int (*pciIterPredicate)(pciDevice *, pciDevice *, void *); + + /* Iterate over available PCI devices calling @predicate + * to compare each one to @dev. +@@ -236,7 +237,8 @@ static int + pciIterDevices(virConnectPtr conn, + pciIterPredicate predicate, + pciDevice *dev, +- pciDevice **matched) ++ pciDevice **matched, ++ void *data) + { + DIR *dir; + struct dirent *entry; +@@ -254,7 +256,7 @@ pciIterDevices(virConnectPtr conn, + + while ((entry = readdir(dir))) { + unsigned domain, bus, slot, function; +- pciDevice *try; ++ pciDevice *check; + + /* Ignore '.' and '..' */ + if (entry->d_name[0] == '.') +@@ -266,18 +268,18 @@ pciIterDevices(virConnectPtr conn, + continue; + } + +- try = pciGetDevice(conn, domain, bus, slot, function); +- if (!try) { ++ check = pciGetDevice(conn, domain, bus, slot, function); ++ if (!check) { + ret = -1; + break; + } + +- if (predicate(try, dev)) { +- VIR_DEBUG("%s %s: iter matched on %s", dev->id, dev->name, try->name); +- *matched = try; ++ if (predicate(dev, check, data)) { ++ VIR_DEBUG("%s %s: iter matched on %s", dev->id, dev->name, check->name); ++ *matched = check; + break; + } +- pciFreeDevice(conn, try); ++ pciFreeDevice(conn, check); + } + closedir(dir); + return ret; +@@ -379,63 +381,70 @@ pciDetectPowerManagementReset(pciDevice *dev) + return 0; + } + +-/* Any devices other than the one supplied on the same domain/bus ? */ ++/* Any active devices other than the one supplied on the same domain/bus ? */ + static int +-pciSharesBus(pciDevice *a, pciDevice *b) ++pciSharesBusWithActive(pciDevice *dev, pciDevice *check, void *data) + { +- return +- a->domain == b->domain && +- a->bus == b->bus && +- (a->slot != b->slot || +- a->function != b->function); +-} ++ pciDeviceList *activeDevs = data; + +-static int +-pciBusContainsOtherDevices(virConnectPtr conn, pciDevice *dev) +-{ +- pciDevice *matched = NULL; +- if (pciIterDevices(conn, pciSharesBus, dev, &matched) < 0) +- return 1; +- if (!matched) ++ if (dev->domain != check->domain || ++ dev->bus != check->bus || ++ (check->slot == check->slot && ++ check->function == check->function)) ++ return 0; ++ ++ if (activeDevs && !pciDeviceListFind(activeDevs, check)) + return 0; +- pciFreeDevice(conn, matched); ++ + return 1; + } + +-/* Is @a the parent of @b ? */ ++static pciDevice * ++pciBusContainsActiveDevices(virConnectPtr conn, ++ pciDevice *dev, ++ pciDeviceList *activeDevs) ++{ ++ pciDevice *active = NULL; ++ if (pciIterDevices(conn, pciSharesBusWithActive, ++ dev, &active, activeDevs) < 0) ++ return NULL; ++ return active; ++} ++ ++/* Is @check the parent of @dev ? */ + static int +-pciIsParent(pciDevice *a, pciDevice *b) ++pciIsParent(pciDevice *dev, pciDevice *check, void *data ATTRIBUTE_UNUSED) + { + uint16_t device_class; + uint8_t header_type, secondary, subordinate; + +- if (a->domain != b->domain) ++ if (dev->domain != check->domain) + return 0; + + /* Is it a bridge? */ +- device_class = pciRead16(a, PCI_CLASS_DEVICE); ++ device_class = pciRead16(check, PCI_CLASS_DEVICE); + if (device_class != PCI_CLASS_BRIDGE_PCI) + return 0; + + /* Is it a plane? */ +- header_type = pciRead8(a, PCI_HEADER_TYPE); ++ header_type = pciRead8(check, PCI_HEADER_TYPE); + if ((header_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) + return 0; + +- secondary = pciRead8(a, PCI_SECONDARY_BUS); +- subordinate = pciRead8(a, PCI_SUBORDINATE_BUS); ++ secondary = pciRead8(check, PCI_SECONDARY_BUS); ++ subordinate = pciRead8(check, PCI_SUBORDINATE_BUS); + +- VIR_DEBUG("%s %s: found parent device %s\n", b->id, b->name, a->name); ++ VIR_DEBUG("%s %s: found parent device %s\n", dev->id, dev->name, check->name); + + /* No, it's superman! */ +- return (b->bus >= secondary && b->bus <= subordinate); ++ return (dev->bus >= secondary && dev->bus <= subordinate); + } + + static pciDevice * + pciGetParentDevice(virConnectPtr conn, pciDevice *dev) + { + pciDevice *parent = NULL; +- pciIterDevices(conn, pciIsParent, dev, &parent); ++ pciIterDevices(conn, pciIsParent, dev, &parent, NULL); + return parent; + } + +@@ -443,9 +452,11 @@ pciGetParentDevice(virConnectPtr conn, pciDevice *dev) + * devices behind a bus. + */ + static int +-pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) ++pciTrySecondaryBusReset(virConnectPtr conn, ++ pciDevice *dev, ++ pciDeviceList *activeDevs) + { +- pciDevice *parent; ++ pciDevice *parent, *conflict; + uint8_t config_space[PCI_CONF_LEN]; + uint16_t ctl; + int ret = -1; +@@ -455,10 +466,10 @@ pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) + * In future, we could allow it so long as those devices + * are not in use by the host or other guests. + */ +- if (pciBusContainsOtherDevices(conn, dev)) { ++ if ((conflict = pciBusContainsActiveDevices(conn, dev, activeDevs))) { + pciReportError(conn, VIR_ERR_NO_SUPPORT, +- _("Other devices on bus with %s, not doing bus reset"), +- dev->name); ++ _("Active %s devices on bus with %s, not doing bus reset"), ++ conflict->name, dev->name); + return -1; + } + +@@ -572,10 +583,18 @@ pciInitDevice(virConnectPtr conn, pciDevice *dev) + } + + int +-pciResetDevice(virConnectPtr conn, pciDevice *dev) ++pciResetDevice(virConnectPtr conn, ++ pciDevice *dev, ++ pciDeviceList *activeDevs) + { + int ret = -1; + ++ if (activeDevs && pciDeviceListFind(activeDevs, dev)) { ++ pciReportError(conn, VIR_ERR_INTERNAL_ERROR, ++ _("Not resetting active device %s"), dev->name); ++ return -1; ++ } ++ + if (!dev->initted && pciInitDevice(conn, dev) < 0) + return -1; + +@@ -594,7 +613,7 @@ pciResetDevice(virConnectPtr conn, pciDevice *dev) + + /* Bus reset is not an option with the root bus */ + if (ret < 0 && dev->bus != 0) +- ret = pciTrySecondaryBusReset(conn, dev); ++ ret = pciTrySecondaryBusReset(conn, dev, activeDevs); + + if (ret < 0) { + virErrorPtr err = virGetLastError(); +@@ -890,8 +909,116 @@ pciGetDevice(virConnectPtr conn, + void + pciFreeDevice(virConnectPtr conn ATTRIBUTE_UNUSED, pciDevice *dev) + { ++ if (!dev) ++ return; + VIR_DEBUG("%s %s: freeing", dev->id, dev->name); + if (dev->fd >= 0) + close(dev->fd); + VIR_FREE(dev); + } ++ ++void pciDeviceSetManaged(pciDevice *dev, unsigned managed) ++{ ++ dev->managed = !!managed; ++} ++ ++unsigned pciDeviceGetManaged(pciDevice *dev) ++{ ++ return dev->managed; ++} ++ ++pciDeviceList * ++pciDeviceListNew(virConnectPtr conn) ++{ ++ pciDeviceList *list; ++ ++ if (VIR_ALLOC(list) < 0) { ++ virReportOOMError(conn); ++ return NULL; ++ } ++ ++ return list; ++} ++ ++void ++pciDeviceListFree(virConnectPtr conn, ++ pciDeviceList *list) ++{ ++ int i; ++ ++ if (!list) ++ return; ++ ++ for (i = 0; i < list->count; i++) { ++ pciFreeDevice(conn, list->devs[i]); ++ list->devs[i] = NULL; ++ } ++ ++ list->count = 0; ++ VIR_FREE(list->devs); ++ VIR_FREE(list); ++} ++ ++int ++pciDeviceListAdd(virConnectPtr conn, ++ pciDeviceList *list, ++ pciDevice *dev) ++{ ++ if (pciDeviceListFind(list, dev)) { ++ pciReportError(conn, VIR_ERR_INTERNAL_ERROR, ++ _("Device %s is already in use"), dev->name); ++ return -1; ++ } ++ ++ if (VIR_REALLOC_N(list->devs, list->count+1) < 0) { ++ virReportOOMError(conn); ++ return -1; ++ } ++ ++ list->devs[list->count++] = dev; ++ ++ return 0; ++} ++ ++void ++pciDeviceListDel(virConnectPtr conn ATTRIBUTE_UNUSED, ++ pciDeviceList *list, ++ pciDevice *dev) ++{ ++ int i; ++ ++ for (i = 0; i < list->count; i++) { ++ if (list->devs[i]->domain != dev->domain || ++ list->devs[i]->bus != dev->bus || ++ list->devs[i]->slot != dev->slot || ++ list->devs[i]->function != dev->function) ++ continue; ++ ++ pciFreeDevice(conn, list->devs[i]); ++ ++ if (i != --list->count) ++ memmove(&list->devs[i], ++ &list->devs[i+1], ++ sizeof(*list->devs) * (list->count-i)); ++ ++ if (VIR_REALLOC_N(list->devs, list->count) < 0) { ++ ; /* not fatal */ ++ } ++ ++ break; ++ } ++} ++ ++pciDevice * ++pciDeviceListFind(pciDeviceList *list, pciDevice *dev) ++{ ++ int i; ++ ++ for (i = 0; i < list->count; i++) ++ if (list->devs[i]->domain == dev->domain && ++ list->devs[i]->bus == dev->bus && ++ list->devs[i]->slot == dev->slot && ++ list->devs[i]->function == dev->function) ++ return list->devs[i]; ++ return NULL; ++} +diff --git a/src/pci.h b/src/pci.h +index 47882ef..685b0af 100644 +--- a/src/pci.h ++++ b/src/pci.h +@@ -27,6 +27,11 @@ + + typedef struct _pciDevice pciDevice; + ++typedef struct { ++ unsigned count; ++ pciDevice **devs; ++} pciDeviceList; ++ + pciDevice *pciGetDevice (virConnectPtr conn, + unsigned domain, + unsigned bus, +@@ -39,6 +44,22 @@ int pciDettachDevice (virConnectPtr conn, + int pciReAttachDevice (virConnectPtr conn, + pciDevice *dev); + int pciResetDevice (virConnectPtr conn, +- pciDevice *dev); ++ pciDevice *dev, ++ pciDeviceList *activeDevs); ++void pciDeviceSetManaged(pciDevice *dev, ++ unsigned managed); ++unsigned pciDeviceGetManaged(pciDevice *dev); ++ ++pciDeviceList *pciDeviceListNew (virConnectPtr conn); ++void pciDeviceListFree (virConnectPtr conn, ++ pciDeviceList *list); ++int pciDeviceListAdd (virConnectPtr conn, ++ pciDeviceList *list, ++ pciDevice *dev); ++void pciDeviceListDel (virConnectPtr conn, ++ pciDeviceList *list, ++ pciDevice *dev); ++pciDevice * pciDeviceListFind (pciDeviceList *list, ++ pciDevice *dev); + + #endif /* __VIR_PCI_H__ */ +diff --git a/src/qemu_conf.h b/src/qemu_conf.h +index 517626a..ab9d5e1 100644 +--- a/src/qemu_conf.h ++++ b/src/qemu_conf.h +@@ -35,6 +35,7 @@ + #include "threads.h" + #include "security.h" + #include "cgroup.h" ++#include "pci.h" + + #define qemudDebug(fmt, ...) do {} while(0) + +@@ -107,6 +108,8 @@ struct qemud_driver { + + char *securityDriverName; + virSecurityDriverPtr securityDriver; ++ ++ pciDeviceList *activePciHostdevs; + }; + + +diff --git a/src/qemu_driver.c b/src/qemu_driver.c +index fd39fc2..cbc27c4 100644 +--- a/src/qemu_driver.c ++++ b/src/qemu_driver.c +@@ -128,6 +128,9 @@ static int qemudDomainSetMemoryBalloon(virConnectPtr conn, + static int qemudDetectVcpuPIDs(virConnectPtr conn, + virDomainObjPtr vm); + ++static int qemuUpdateActivePciHostdevs(struct qemud_driver *driver, ++ virDomainDefPtr def); ++ + static struct qemud_driver *qemu_driver = NULL; + + static int qemuCgroupControllerActive(struct qemud_driver *driver, +@@ -320,6 +323,10 @@ qemuReconnectDomain(struct qemud_driver *driver, + goto error; + } + ++ if (qemuUpdateActivePciHostdevs(driver, obj->def) < 0) { ++ goto error; ++ } ++ + if (obj->def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC && + driver->securityDriver && + driver->securityDriver->domainReserveSecurityLabel && +@@ -524,6 +531,9 @@ qemudStartup(int privileged) { + if ((qemu_driver->caps = qemudCapsInit(NULL)) == NULL) + goto out_of_memory; + ++ if ((qemu_driver->activePciHostdevs = pciDeviceListNew(NULL)) == NULL) ++ goto error; ++ + if (qemudLoadDriverConfig(qemu_driver, driverConf) < 0) { + goto error; + } +@@ -648,6 +658,7 @@ qemudShutdown(void) { + return -1; + + qemuDriverLock(qemu_driver); ++ pciDeviceListFree(NULL, qemu_driver->activePciHostdevs); + virCapabilitiesFree(qemu_driver->caps); + + virDomainObjListFree(&qemu_driver->domains); +@@ -1329,48 +1340,16 @@ static int qemudNextFreeVNCPort(struct qemud_driver *driver ATTRIBUTE_UNUSED) { + return -1; + } + +-static int qemuPrepareHostDevices(virConnectPtr conn, +- virDomainDefPtr def) { ++static pciDeviceList * ++qemuGetPciHostDeviceList(virConnectPtr conn, ++ virDomainDefPtr def) ++{ ++ pciDeviceList *list; + int i; + +- /* We have to use 2 loops here. *All* devices must +- * be detached before we reset any of them, because +- * in some cases you have to reset the whole PCI, +- * which impacts all devices on it +- */ +- +- for (i = 0 ; i < def->nhostdevs ; i++) { +- virDomainHostdevDefPtr hostdev = def->hostdevs[i]; +- +- if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) +- continue; +- if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) +- continue; +- +- if (hostdev->managed) { +- pciDevice *dev = pciGetDevice(conn, +- hostdev->source.subsys.u.pci.domain, +- hostdev->source.subsys.u.pci.bus, +- hostdev->source.subsys.u.pci.slot, +- hostdev->source.subsys.u.pci.function); +- if (!dev) +- goto error; +- +- if (pciDettachDevice(conn, dev) < 0) { +- pciFreeDevice(conn, dev); +- goto error; +- } +- +- pciFreeDevice(conn, dev); +- } /* else { +- XXX validate that non-managed device isn't in use, eg +- by checking that device is either un-bound, or bound +- to pci-stub.ko +- } */ +- } ++ if (!(list = pciDeviceListNew(conn))) ++ return NULL; + +- /* Now that all the PCI hostdevs have be dettached, we can safely +- * reset them */ + for (i = 0 ; i < def->nhostdevs ; i++) { + virDomainHostdevDefPtr hostdev = def->hostdevs[i]; + pciDevice *dev; +@@ -1385,95 +1364,151 @@ static int qemuPrepareHostDevices(virConnectPtr conn, + hostdev->source.subsys.u.pci.bus, + hostdev->source.subsys.u.pci.slot, + hostdev->source.subsys.u.pci.function); +- if (!dev) +- goto error; ++ if (!dev) { ++ pciDeviceListFree(conn, list); ++ return NULL; ++ } + +- if (pciResetDevice(conn, dev) < 0) { ++ if (pciDeviceListAdd(conn, list, dev) < 0) { + pciFreeDevice(conn, dev); +- goto error; ++ pciDeviceListFree(conn, list); ++ return NULL; + } + +- pciFreeDevice(conn, dev); ++ pciDeviceSetManaged(dev, hostdev->managed); + } + +- return 0; ++ return list; ++} + +-error: +- return -1; ++static int ++qemuUpdateActivePciHostdevs(struct qemud_driver *driver, ++ virDomainDefPtr def) ++{ ++ pciDeviceList *pcidevs; ++ int i, ret; ++ ++ if (!def->nhostdevs) ++ return 0; ++ ++ if (!(pcidevs = qemuGetPciHostDeviceList(NULL, def))) ++ return -1; ++ ++ ret = 0; ++ ++ for (i = 0; i < pcidevs->count; i++) { ++ if (pciDeviceListAdd(NULL, ++ driver->activePciHostdevs, ++ pcidevs->devs[i]) < 0) { ++ ret = -1; ++ break; ++ } ++ pcidevs->devs[i] = NULL; ++ } ++ ++ pciDeviceListFree(NULL, pcidevs); ++ return ret; + } + +-static void +-qemuDomainReAttachHostDevices(virConnectPtr conn, virDomainDefPtr def) ++static int ++qemuPrepareHostDevices(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainDefPtr def) + { ++ pciDeviceList *pcidevs; + int i; + +- /* Again 2 loops; reset all the devices before re-attach */ ++ if (!def->nhostdevs) ++ return 0; + +- for (i = 0 ; i < def->nhostdevs ; i++) { +- virDomainHostdevDefPtr hostdev = def->hostdevs[i]; +- pciDevice *dev; ++ if (!(pcidevs = qemuGetPciHostDeviceList(conn, def))) ++ return -1; + +- if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) +- continue; +- if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) +- continue; ++ /* We have to use 3 loops here. *All* devices must ++ * be detached before we reset any of them, because ++ * in some cases you have to reset the whole PCI, ++ * which impacts all devices on it. Also, all devices ++ * must be reset before being marked as active. ++ */ + +- dev = pciGetDevice(conn, +- hostdev->source.subsys.u.pci.domain, +- hostdev->source.subsys.u.pci.bus, +- hostdev->source.subsys.u.pci.slot, +- hostdev->source.subsys.u.pci.function); +- if (!dev) { +- virErrorPtr err = virGetLastError(); +- VIR_ERROR(_("Failed to allocate pciDevice: %s\n"), +- err ? err->message : ""); +- virResetError(err); +- continue; +- } ++ /* XXX validate that non-managed device isn't in use, eg ++ * by checking that device is either un-bound, or bound ++ * to pci-stub.ko ++ */ + +- if (pciResetDevice(conn, dev) < 0) { +- virErrorPtr err = virGetLastError(); +- VIR_ERROR(_("Failed to reset PCI device: %s\n"), +- err ? err->message : ""); +- virResetError(err); +- } ++ for (i = 0; i < pcidevs->count; i++) ++ if (pciDeviceGetManaged(pcidevs->devs[i]) && ++ pciDettachDevice(conn, pcidevs->devs[i]) < 0) ++ goto error; ++ ++ /* Now that all the PCI hostdevs have be dettached, we can safely ++ * reset them */ ++ for (i = 0; i < pcidevs->count; i++) ++ if (pciResetDevice(conn, pcidevs->devs[i], ++ driver->activePciHostdevs) < 0) ++ goto error; + +- pciFreeDevice(conn, dev); ++ /* Now mark all the devices as active */ ++ for (i = 0; i < pcidevs->count; i++) { ++ if (pciDeviceListAdd(conn, ++ driver->activePciHostdevs, ++ pcidevs->devs[i]) < 0) ++ goto error; ++ pcidevs->devs[i] = NULL; + } + +- for (i = 0 ; i < def->nhostdevs ; i++) { +- virDomainHostdevDefPtr hostdev = def->hostdevs[i]; +- pciDevice *dev; ++ pciDeviceListFree(conn, pcidevs); ++ return 0; + +- if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) +- continue; +- if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) +- continue; +- if (!hostdev->managed) +- continue; ++error: ++ pciDeviceListFree(conn, pcidevs); ++ return -1; ++} + +- dev = pciGetDevice(conn, +- hostdev->source.subsys.u.pci.domain, +- hostdev->source.subsys.u.pci.bus, +- hostdev->source.subsys.u.pci.slot, +- hostdev->source.subsys.u.pci.function); +- if (!dev) { ++static void ++qemuDomainReAttachHostDevices(virConnectPtr conn, ++ struct qemud_driver *driver, ++ virDomainDefPtr def) ++{ ++ pciDeviceList *pcidevs; ++ int i; ++ ++ if (!def->nhostdevs) ++ return; ++ ++ if (!(pcidevs = qemuGetPciHostDeviceList(conn, def))) { ++ virErrorPtr err = virGetLastError(); ++ VIR_ERROR(_("Failed to allocate pciDeviceList: %s\n"), ++ err ? err->message : ""); ++ virResetError(err); ++ return; ++ } ++ ++ /* Again 3 loops; mark all devices as inactive before reset ++ * them and reset all the devices before re-attach */ ++ ++ for (i = 0; i < pcidevs->count; i++) ++ pciDeviceListDel(conn, driver->activePciHostdevs, pcidevs->devs[i]); ++ ++ for (i = 0; i < pcidevs->count; i++) ++ if (pciResetDevice(conn, pcidevs->devs[i], ++ driver->activePciHostdevs) < 0) { + virErrorPtr err = virGetLastError(); +- VIR_ERROR(_("Failed to allocate pciDevice: %s\n"), ++ VIR_ERROR(_("Failed to reset PCI device: %s\n"), + err ? err->message : ""); + virResetError(err); +- continue; + } + +- if (pciReAttachDevice(conn, dev) < 0) { ++ for (i = 0; i < pcidevs->count; i++) ++ if (pciDeviceGetManaged(pcidevs->devs[i]) && ++ pciReAttachDevice(conn, pcidevs->devs[i]) < 0) { + virErrorPtr err = virGetLastError(); + VIR_ERROR(_("Failed to re-attach PCI device: %s\n"), + err ? err->message : ""); + virResetError(err); + } + +- pciFreeDevice(conn, dev); +- } ++ pciDeviceListFree(conn, pcidevs); + } + + static const char *const defaultDeviceACL[] = { +@@ -2001,7 +2036,7 @@ static int qemudStartVMDaemon(virConnectPtr conn, + if (qemuSetupCgroup(conn, driver, vm) < 0) + goto cleanup; + +- if (qemuPrepareHostDevices(conn, vm->def) < 0) ++ if (qemuPrepareHostDevices(conn, driver, vm->def) < 0) + goto cleanup; + + if (VIR_ALLOC(vm->monitor_chr) < 0) { +@@ -2183,7 +2218,7 @@ static void qemudShutdownVMDaemon(virConnectPtr conn, + VIR_WARN("Failed to restore all device ownership for %s", + vm->def->name); + +- qemuDomainReAttachHostDevices(conn, vm->def); ++ qemuDomainReAttachHostDevices(conn, driver, vm->def); + + retry: + if ((ret = qemuRemoveCgroup(conn, driver, vm)) < 0) { +@@ -6791,6 +6826,7 @@ out: + static int + qemudNodeDeviceReset (virNodeDevicePtr dev) + { ++ struct qemud_driver *driver = dev->conn->privateData; + pciDevice *pci; + unsigned domain, bus, slot, function; + int ret = -1; +@@ -6802,11 +6838,14 @@ qemudNodeDeviceReset (virNodeDevicePtr dev) + if (!pci) + return -1; + +- if (pciResetDevice(dev->conn, pci) < 0) ++ qemuDriverLock(driver); ++ ++ if (pciResetDevice(dev->conn, pci, driver->activePciHostdevs) < 0) + goto out; + + ret = 0; + out: ++ qemuDriverUnlock(driver); + pciFreeDevice(dev->conn, pci); + return ret; + } +diff --git a/src/xen_unified.c b/src/xen_unified.c +index f2ffc25..dfa9ca5 100644 +--- a/src/xen_unified.c ++++ b/src/xen_unified.c +@@ -1641,7 +1641,7 @@ xenUnifiedNodeDeviceReset (virNodeDevicePtr dev) + if (!pci) + return -1; + +- if (pciResetDevice(dev->conn, pci) < 0) ++ if (pciResetDevice(dev->conn, pci, NULL) < 0) + goto out; + + ret = 0; +-- +1.6.2.5 + diff --git a/libvirt-allow-pm-reset-on-multi-function-pci-devices.patch b/libvirt-allow-pm-reset-on-multi-function-pci-devices.patch new file mode 100644 index 0000000..601165d --- /dev/null +++ b/libvirt-allow-pm-reset-on-multi-function-pci-devices.patch @@ -0,0 +1,121 @@ +From 5aad00b08cadc4d9da8bffd3d255ffaac98d36dd Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Fri, 14 Aug 2009 08:31:11 +0100 +Subject: [PATCH] Allow PM reset on multi-function PCI devices + +https://bugzilla.redhat.com/515689 + +It turns out that a PCI Power Management reset only affects individual +functions, and not the whole device. + +The PCI Power Management spec talks about resetting the 'device' rather +than the 'function', but Intel's Dexuan Cui informs me that it is +actually a per-function reset. + +Also, Yu Zhao has added pci_pm_reset() to the kernel, and it doesn't +reject multi-function devices, so it must be true! :-) + +(A side issue is that we could defer the PM reset to the kernel if we +could detect that the kernel has PM reset support, but barring version +number checks we don't have a way to detect that support) + +* src/pci.c: remove the pciDeviceContainsOtherFunctions() check from + pciTryPowerManagementReset() and prefer PM reset over bus reset + where both are available + +Cc: Cui, Dexuan +Cc: Yu Zhao + +(cherry picked from commit 64a6682b93a2a8aa38067a43979c9eaf993d2b41) + +Fedora-patch: libvirt-allow-pm-reset-on-multi-function-pci-devices.patch +--- + src/pci.c | 48 +++++++++--------------------------------------- + 1 files changed, 9 insertions(+), 39 deletions(-) + +diff --git a/src/pci.c b/src/pci.c +index 2dc2e1c..11b3e8b 100644 +--- a/src/pci.c ++++ b/src/pci.c +@@ -402,29 +402,6 @@ pciBusContainsOtherDevices(virConnectPtr conn, pciDevice *dev) + return 1; + } + +-/* Any other functions on this device ? */ +-static int +-pciSharesDevice(pciDevice *a, pciDevice *b) +-{ +- return +- a->domain == b->domain && +- a->bus == b->bus && +- a->slot == b->slot && +- a->function != b->function; +-} +- +-static int +-pciDeviceContainsOtherFunctions(virConnectPtr conn, pciDevice *dev) +-{ +- pciDevice *matched = NULL; +- if (pciIterDevices(conn, pciSharesDevice, dev, &matched) < 0) +- return 1; +- if (!matched) +- return 0; +- pciFreeDevice(conn, matched); +- return 1; +-} +- + /* Is @a the parent of @b ? */ + static int + pciIsParent(pciDevice *a, pciDevice *b) +@@ -529,7 +506,7 @@ out: + * above we require the device supports a full internal reset. + */ + static int +-pciTryPowerManagementReset(virConnectPtr conn, pciDevice *dev) ++pciTryPowerManagementReset(virConnectPtr conn ATTRIBUTE_UNUSED, pciDevice *dev) + { + uint8_t config_space[PCI_CONF_LEN]; + uint32_t ctl; +@@ -537,16 +514,6 @@ pciTryPowerManagementReset(virConnectPtr conn, pciDevice *dev) + if (!dev->pci_pm_cap_pos) + return -1; + +- /* For now, we just refuse to do a power management reset +- * if there are other functions on this device. +- * In future, we could allow it so long as those functions +- * are not in use by the host or other guests. +- */ +- if (pciDeviceContainsOtherFunctions(conn, dev)) { +- VIR_WARN("%s contains other functions, not resetting", dev->name); +- return -1; +- } +- + /* Save and restore the device's config space. */ + if (pciRead(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) { + VIR_WARN("Failed to save PCI config space for %s", dev->name); +@@ -604,14 +571,17 @@ pciResetDevice(virConnectPtr conn, pciDevice *dev) + if (dev->has_flr) + return 0; + ++ /* If the device supports PCI power management reset, ++ * that's the next best thing because it only resets ++ * the function, not the whole device. ++ */ ++ if (dev->has_pm_reset) ++ ret = pciTryPowerManagementReset(conn, dev); ++ + /* Bus reset is not an option with the root bus */ +- if (dev->bus != 0) ++ if (ret < 0 && dev->bus != 0) + ret = pciTrySecondaryBusReset(conn, dev); + +- /* Next best option is a PCI power management reset */ +- if (ret < 0 && dev->has_pm_reset) +- ret = pciTryPowerManagementReset(conn, dev); +- + if (ret < 0) + pciReportError(conn, VIR_ERR_NO_SUPPORT, + _("No PCI reset capability available for %s"), +-- +1.6.2.5 + diff --git a/libvirt-fix-device-list-update-after-detach.patch b/libvirt-fix-device-list-update-after-detach.patch new file mode 100644 index 0000000..76598d6 --- /dev/null +++ b/libvirt-fix-device-list-update-after-detach.patch @@ -0,0 +1,79 @@ +From 165fb333c9d954fec636dc0f1917ba50417478c0 Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Sat, 15 Aug 2009 19:38:15 +0100 +Subject: [PATCH] Fix list updating after disk/network hot-unplug + +The current code makes a poor effort at updating the device arrays after +hot-unplug. Fix that and combine the two code paths into one. + +* src/qemu_driver.c: fix list updating in qemudDomainDetachNetDevice() and + qemudDomainDetachPciDiskDevice() + +(cherry picked from commit 4e12af5623e4a962a6bb911af06fa29aa85befba) + +Fedora-patch: libvirt-fix-device-list-update-after-detach.patch +--- + src/qemu_driver.c | 38 ++++++++++++++++++-------------------- + 1 files changed, 18 insertions(+), 20 deletions(-) + +diff --git a/src/qemu_driver.c b/src/qemu_driver.c +index bd58435..2c4fd6f 100644 +--- a/src/qemu_driver.c ++++ b/src/qemu_driver.c +@@ -5404,18 +5404,17 @@ try_command: + goto cleanup; + } + +- if (vm->def->ndisks > 1) { +- vm->def->disks[i] = vm->def->disks[--vm->def->ndisks]; +- if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks) < 0) { +- virReportOOMError(conn); +- goto cleanup; +- } +- qsort(vm->def->disks, vm->def->ndisks, sizeof(*vm->def->disks), +- virDomainDiskQSort); +- } else { +- VIR_FREE(vm->def->disks[0]); +- vm->def->ndisks = 0; ++ if (i != --vm->def->ndisks) ++ memmove(&vm->def->disks[i], ++ &vm->def->disks[i+1], ++ sizeof(*vm->def->disks) * (vm->def->ndisks-i)); ++ if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks) < 0) { ++ virReportOOMError(conn); ++ goto cleanup; + } ++ qsort(vm->def->disks, vm->def->ndisks, sizeof(*vm->def->disks), ++ virDomainDiskQSort); ++ + ret = 0; + + cleanup: +@@ -5503,16 +5502,15 @@ qemudDomainDetachNetDevice(virConnectPtr conn, + + DEBUG("%s: host_net_remove reply: %s", vm->def->name, reply); + +- if (vm->def->nnets > 1) { +- vm->def->nets[i] = vm->def->nets[--vm->def->nnets]; +- if (VIR_REALLOC_N(vm->def->nets, vm->def->nnets) < 0) { +- virReportOOMError(conn); +- goto cleanup; +- } +- } else { +- VIR_FREE(vm->def->nets[0]); +- vm->def->nnets = 0; ++ if (i != --vm->def->nnets) ++ memmove(&vm->def->nets[i], ++ &vm->def->nets[i+1], ++ sizeof(*vm->def->nets) * (vm->def->nnets-i)); ++ if (VIR_REALLOC_N(vm->def->nets, vm->def->nnets) < 0) { ++ virReportOOMError(conn); ++ goto cleanup; + } ++ + ret = 0; + + cleanup: +-- +1.6.2.5 + diff --git a/libvirt-improve-pci-hostdev-reset-error-message.patch b/libvirt-improve-pci-hostdev-reset-error-message.patch new file mode 100644 index 0000000..bda054b --- /dev/null +++ b/libvirt-improve-pci-hostdev-reset-error-message.patch @@ -0,0 +1,141 @@ +From c8a9dbe131713de83bdc67c563c4ab149e32c489 Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Fri, 14 Aug 2009 08:31:11 +0100 +Subject: [PATCH] Improve PCI host device reset error message + +https://bugzilla.redhat.com/499678 + +Currently, if we are unable to reset a PCI device we return a fairly +generic 'No PCI reset capability available' error message. + +Fix that by returning an error from the individual reset messages and +using that error to construct the higher level error mesage. + +* src/pci.c: set errors in pciTryPowerManagementReset() and + pciTrySecondaryBusReset() on failure; use those error messages + in pciResetDevice(), or explain that no reset support is available + +(cherry picked from commit ebea34185612c3b96d7d3bbd8b7c2ce6c9f4fe6f) + +Fedora-patch: libvirt-improve-pci-hostdev-reset-error-message.patch +--- + src/pci.c | 44 +++++++++++++++++++++++++++++++------------- + src/qemu_driver.c | 4 ++-- + 2 files changed, 33 insertions(+), 15 deletions(-) + +diff --git a/src/pci.c b/src/pci.c +index 11b3e8b..74f7ef0 100644 +--- a/src/pci.c ++++ b/src/pci.c +@@ -456,15 +456,18 @@ pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) + * are not in use by the host or other guests. + */ + if (pciBusContainsOtherDevices(conn, dev)) { +- VIR_WARN("Other devices on bus with %s, not doing bus reset", +- dev->name); ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Other devices on bus with %s, not doing bus reset"), ++ dev->name); + return -1; + } + + /* Find the parent bus */ + parent = pciGetParentDevice(conn, dev); + if (!parent) { +- VIR_WARN("Failed to find parent device for %s", dev->name); ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Failed to find parent device for %s"), ++ dev->name); + return -1; + } + +@@ -475,7 +478,9 @@ pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) + * are multiple devices/functions + */ + if (pciRead(dev, 0, config_space, PCI_CONF_LEN) < 0) { +- VIR_WARN("Failed to save PCI config space for %s", dev->name); ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Failed to save PCI config space for %s"), ++ dev->name); + goto out; + } + +@@ -492,9 +497,12 @@ pciTrySecondaryBusReset(virConnectPtr conn, pciDevice *dev) + + usleep(200 * 1000); /* sleep 200ms */ + +- if (pciWrite(dev, 0, config_space, PCI_CONF_LEN) < 0) +- VIR_WARN("Failed to restore PCI config space for %s", dev->name); +- ++ if (pciWrite(dev, 0, config_space, PCI_CONF_LEN) < 0) { ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Failed to restore PCI config space for %s"), ++ dev->name); ++ goto out; ++ } + ret = 0; + out: + pciFreeDevice(conn, parent); +@@ -516,7 +524,9 @@ pciTryPowerManagementReset(virConnectPtr conn ATTRIBUTE_UNUSED, pciDevice *dev) + + /* Save and restore the device's config space. */ + if (pciRead(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) { +- VIR_WARN("Failed to save PCI config space for %s", dev->name); ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Failed to save PCI config space for %s"), ++ dev->name); + return -1; + } + +@@ -533,8 +543,12 @@ pciTryPowerManagementReset(virConnectPtr conn ATTRIBUTE_UNUSED, pciDevice *dev) + + usleep(10 * 1000); /* sleep 10ms */ + +- if (pciWrite(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) +- VIR_WARN("Failed to restore PCI config space for %s", dev->name); ++ if (pciWrite(dev, 0, &config_space[0], PCI_CONF_LEN) < 0) { ++ pciReportError(conn, VIR_ERR_NO_SUPPORT, ++ _("Failed to restore PCI config space for %s"), ++ dev->name); ++ return -1; ++ } + + return 0; + } +@@ -582,10 +596,14 @@ pciResetDevice(virConnectPtr conn, pciDevice *dev) + if (ret < 0 && dev->bus != 0) + ret = pciTrySecondaryBusReset(conn, dev); + +- if (ret < 0) ++ if (ret < 0) { ++ virErrorPtr err = virGetLastError(); + pciReportError(conn, VIR_ERR_NO_SUPPORT, +- _("No PCI reset capability available for %s"), +- dev->name); ++ _("Unable to reset PCI device %s: %s"), ++ dev->name, ++ err ? err->message : _("no FLR, PM reset or bus reset available")); ++ } ++ + return ret; + } + +diff --git a/src/qemu_driver.c b/src/qemu_driver.c +index 4ce7a54..fd39fc2 100644 +--- a/src/qemu_driver.c ++++ b/src/qemu_driver.c +@@ -1465,9 +1465,9 @@ qemuDomainReAttachHostDevices(virConnectPtr conn, virDomainDefPtr def) + continue; + } + +- if (pciDettachDevice(conn, dev) < 0) { ++ if (pciReAttachDevice(conn, dev) < 0) { + virErrorPtr err = virGetLastError(); +- VIR_ERROR(_("Failed to reset PCI device: %s\n"), ++ VIR_ERROR(_("Failed to re-attach PCI device: %s\n"), + err ? err->message : ""); + virResetError(err); + } +-- +1.6.2.5 + diff --git a/libvirt-reattach-pci-hostdevs-after-guest-shutdown.patch b/libvirt-reattach-pci-hostdevs-after-guest-shutdown.patch new file mode 100644 index 0000000..2c79a8c --- /dev/null +++ b/libvirt-reattach-pci-hostdevs-after-guest-shutdown.patch @@ -0,0 +1,124 @@ +From 8f26acc66ca90eea67fd5e84be5a76e3b8aa7fbf Mon Sep 17 00:00:00 2001 +From: Mark McLoughlin +Date: Fri, 14 Aug 2009 08:31:11 +0100 +Subject: [PATCH] Reset and re-attach PCI host devices on guest shutdown + +https://bugzilla.redhat.com/499561 + +When the guest shuts down, we should attempt to restore all PCI host +devices to a sane state. + +In the case of managed hostdevs, we should reset and re-attach the +devices. In the case of unmanaged hostdevs, we should just reset them. + +Note, KVM will already reset assigned devices when the guest shuts +down using whatever means it can, so we are only doing it to cover the +cases the kernel can't handle. + +* src/qemu_driver.c: add qemuDomainReAttachHostDevices() and call + it from qemudShutdownVMDaemon() + +(cherry picked from commit 4035152a8767e72fd4e26a91cb4d5afa75b72e61) + +Fedora-patch: libvirt-reattach-pci-hostdevs-after-guest-shutdown.patch +--- + src/qemu_driver.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 76 insertions(+), 0 deletions(-) + +diff --git a/src/qemu_driver.c b/src/qemu_driver.c +index 2c4fd6f..4ce7a54 100644 +--- a/src/qemu_driver.c ++++ b/src/qemu_driver.c +@@ -1402,6 +1402,80 @@ error: + return -1; + } + ++static void ++qemuDomainReAttachHostDevices(virConnectPtr conn, virDomainDefPtr def) ++{ ++ int i; ++ ++ /* Again 2 loops; reset all the devices before re-attach */ ++ ++ for (i = 0 ; i < def->nhostdevs ; i++) { ++ virDomainHostdevDefPtr hostdev = def->hostdevs[i]; ++ pciDevice *dev; ++ ++ if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) ++ continue; ++ if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) ++ continue; ++ ++ dev = pciGetDevice(conn, ++ hostdev->source.subsys.u.pci.domain, ++ hostdev->source.subsys.u.pci.bus, ++ hostdev->source.subsys.u.pci.slot, ++ hostdev->source.subsys.u.pci.function); ++ if (!dev) { ++ virErrorPtr err = virGetLastError(); ++ VIR_ERROR(_("Failed to allocate pciDevice: %s\n"), ++ err ? err->message : ""); ++ virResetError(err); ++ continue; ++ } ++ ++ if (pciResetDevice(conn, dev) < 0) { ++ virErrorPtr err = virGetLastError(); ++ VIR_ERROR(_("Failed to reset PCI device: %s\n"), ++ err ? err->message : ""); ++ virResetError(err); ++ } ++ ++ pciFreeDevice(conn, dev); ++ } ++ ++ for (i = 0 ; i < def->nhostdevs ; i++) { ++ virDomainHostdevDefPtr hostdev = def->hostdevs[i]; ++ pciDevice *dev; ++ ++ if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) ++ continue; ++ if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) ++ continue; ++ if (!hostdev->managed) ++ continue; ++ ++ dev = pciGetDevice(conn, ++ hostdev->source.subsys.u.pci.domain, ++ hostdev->source.subsys.u.pci.bus, ++ hostdev->source.subsys.u.pci.slot, ++ hostdev->source.subsys.u.pci.function); ++ if (!dev) { ++ virErrorPtr err = virGetLastError(); ++ VIR_ERROR(_("Failed to allocate pciDevice: %s\n"), ++ err ? err->message : ""); ++ virResetError(err); ++ continue; ++ } ++ ++ if (pciDettachDevice(conn, dev) < 0) { ++ virErrorPtr err = virGetLastError(); ++ VIR_ERROR(_("Failed to reset PCI device: %s\n"), ++ err ? err->message : ""); ++ virResetError(err); ++ } ++ ++ pciFreeDevice(conn, dev); ++ } ++} ++ + static const char *const defaultDeviceACL[] = { + "/dev/null", "/dev/full", "/dev/zero", + "/dev/random", "/dev/urandom", +@@ -2109,6 +2183,8 @@ static void qemudShutdownVMDaemon(virConnectPtr conn, + VIR_WARN("Failed to restore all device ownership for %s", + vm->def->name); + ++ qemuDomainReAttachHostDevices(conn, vm->def); ++ + retry: + if ((ret = qemuRemoveCgroup(conn, driver, vm)) < 0) { + if (ret == -EBUSY && (retries++ < 5)) { +-- +1.6.2.5 + diff --git a/libvirt.spec b/libvirt.spec index 56aa8dd..2df152d 100644 --- a/libvirt.spec +++ b/libvirt.spec @@ -96,6 +96,27 @@ Patch03: libvirt-0.7.0-policy-kit-rewrite.patch # Log and ignore NUMA topology problems (rhbz #506590) Patch04: libvirt-0.7.0-numa-ignore-fail.patch +# Minor 'virsh nodedev-list --tree' annoyance, fix from upstream +Patch05: libvirt-add-space-to-nodedev-list-tree.patch + +# Fixes list corruption after disk hot-unplug +Patch06: libvirt-fix-device-list-update-after-detach.patch + +# Re-attach PCI host devices after guest shuts down (bug #499561) +Patch07: libvirt-reattach-pci-hostdevs-after-guest-shutdown.patch + +# Allow PM reset on multi-function PCI devices (bug #515689) +Patch08: libvirt-allow-pm-reset-on-multi-function-pci-devices.patch + +# Fix stupid PCI reset error message (#499678) +Patch09: libvirt-improve-pci-hostdev-reset-error-message.patch + +# Allow PCI bus reset to reset other devices (#499678) +Patch10: libvirt-allow-pci-hostdev-reset-to-reset-other-devices.patch + +# Add PCI host device hotplug support +Patch11: libvirt-add-pci-hostdev-hotplug-support.patch + # Temporary hack till PulseAudio autostart problems are sorted # out when SELinux enforcing (bz 486112) Patch200: libvirt-0.6.4-svirt-sound.patch @@ -281,6 +302,13 @@ of recent versions of Linux (and other OSes). %patch02 -p1 %patch03 -p1 %patch04 -p1 +%patch05 -p1 +%patch06 -p1 +%patch07 -p1 +%patch08 -p1 +%patch09 -p1 +%patch10 -p1 +%patch11 -p1 %patch200 -p1 @@ -649,6 +677,15 @@ fi %endif %changelog +* Wed Aug 19 2009 Mark McLoughlin - 0.7.0-5 +- Add PCI host device hotplug support +- Allow PCI bus reset to reset other devices (#499678) +- Fix stupid PCI reset error message (bug #499678) +- Allow PM reset on multi-function PCI devices (bug #515689) +- Re-attach PCI host devices after guest shuts down (bug #499561) +- Fix list corruption after disk hot-unplug +- Fix minor 'virsh nodedev-list --tree' annoyance + * Thu Aug 13 2009 Daniel P. Berrange - 0.7.0-4 - Rewrite policykit support (rhbz #499970) - Log and ignore NUMA topology problems (rhbz #506590)