mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-28 08:35:22 +00:00
3f29d6c91f
* src/qemu/qemu_hostdev.c (qemuDomainReAttachHostdevDevices): pciDeviceListFree(pcidevs) in the end free()s the device even if it's in use by other domain, which can cause a race. How to reproduce: <script> virsh nodedev-dettach pci_0000_00_19_0 virsh start test virsh attach-device test hostdev.xml virsh start test2 for i in {1..5}; do echo "[ -- ${i}th time --]" virsh nodedev-reattach pci_0000_00_19_0 done echo "clean up" virsh destroy test virsh nodedev-reattach pci_0000_00_19_0 </script> Device pci_0000_00_19_0 dettached Domain test started Device attached successfully error: Failed to start domain test2 error: Requested operation is not valid: PCI device 0000:00:19.0 is in use by domain test [ -- 1th time --] Device pci_0000_00_19_0 re-attached [ -- 2th time --] Device pci_0000_00_19_0 re-attached [ -- 3th time --] Device pci_0000_00_19_0 re-attached [ -- 4th time --] Device pci_0000_00_19_0 re-attached [ -- 5th time --] Device pci_0000_00_19_0 re-attached clean up Domain test destroyed Device pci_0000_00_19_0 re-attached The patch also fixes another problem, there won't be error like "qemuDomainReAttachHostdevDevices: Not reattaching active device 0000:00:19.0" in daemon log if some device is in active. As pciResetDevice and pciReattachDevice won't be called for the device anymore. This is sensible as we already reported error when preparing the device if it's active. Blindly trying to pciResetDevice & pciReattachDevice on the device and getting an error is just redundant.
453 lines
14 KiB
C
453 lines
14 KiB
C
/*
|
|
* qemu_hostdev.c: QEMU hostdev management
|
|
*
|
|
* Copyright (C) 2006-2007, 2009-2011 Red Hat, Inc.
|
|
* Copyright (C) 2006 Daniel P. Berrange
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* Author: Daniel P. Berrange <berrange@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "qemu_hostdev.h"
|
|
#include "logging.h"
|
|
#include "virterror_internal.h"
|
|
#include "memory.h"
|
|
#include "pci.h"
|
|
#include "hostusb.h"
|
|
|
|
static pciDeviceList *
|
|
qemuGetPciHostDeviceList(virDomainHostdevDefPtr *hostdevs, int nhostdevs)
|
|
{
|
|
pciDeviceList *list;
|
|
int i;
|
|
|
|
if (!(list = pciDeviceListNew()))
|
|
return NULL;
|
|
|
|
for (i = 0 ; i < nhostdevs ; i++) {
|
|
virDomainHostdevDefPtr hostdev = 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(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) {
|
|
pciDeviceListFree(list);
|
|
return NULL;
|
|
}
|
|
|
|
if (pciDeviceListAdd(list, dev) < 0) {
|
|
pciFreeDevice(dev);
|
|
pciDeviceListFree(list);
|
|
return NULL;
|
|
}
|
|
|
|
pciDeviceSetManaged(dev, hostdev->managed);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static pciDeviceList *
|
|
qemuGetActivePciHostDeviceList(struct qemud_driver *driver,
|
|
virDomainHostdevDefPtr *hostdevs,
|
|
int nhostdevs)
|
|
{
|
|
pciDeviceList *list;
|
|
int i;
|
|
|
|
if (!(list = pciDeviceListNew()))
|
|
return NULL;
|
|
|
|
for (i = 0 ; i < nhostdevs ; i++) {
|
|
virDomainHostdevDefPtr hostdev = hostdevs[i];
|
|
pciDevice *dev;
|
|
pciDevice *activeDev;
|
|
|
|
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
|
|
continue;
|
|
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI)
|
|
continue;
|
|
|
|
dev = pciGetDevice(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) {
|
|
pciDeviceListFree(list);
|
|
return NULL;
|
|
}
|
|
|
|
if ((activeDev = pciDeviceListFind(driver->activePciHostdevs, dev))) {
|
|
if (pciDeviceListAdd(list, activeDev) < 0) {
|
|
pciFreeDevice(dev);
|
|
pciDeviceListFree(list);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
pciFreeDevice(dev);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
int qemuUpdateActivePciHostdevs(struct qemud_driver *driver,
|
|
virDomainDefPtr def)
|
|
{
|
|
virDomainHostdevDefPtr hostdev = NULL;
|
|
int i;
|
|
|
|
if (!def->nhostdevs)
|
|
return 0;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
pciDevice *dev = NULL;
|
|
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;
|
|
|
|
dev = pciGetDevice(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;
|
|
|
|
pciDeviceSetManaged(dev, hostdev->managed);
|
|
pciDeviceSetUsedBy(dev, def->name);
|
|
|
|
/* Setup the original states for the PCI device */
|
|
pciDeviceSetUnbindFromStub(dev, hostdev->origstates.states.pci.unbind_from_stub);
|
|
pciDeviceSetRemoveSlot(dev, hostdev->origstates.states.pci.remove_slot);
|
|
pciDeviceSetReprobe(dev, hostdev->origstates.states.pci.reprobe);
|
|
|
|
if (pciDeviceListAdd(driver->activePciHostdevs, dev) < 0) {
|
|
pciFreeDevice(dev);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuPrepareHostdevPCIDevices(struct qemud_driver *driver,
|
|
const char *name,
|
|
virDomainHostdevDefPtr *hostdevs,
|
|
int nhostdevs)
|
|
{
|
|
pciDeviceList *pcidevs;
|
|
int i;
|
|
int ret = -1;
|
|
|
|
if (!(pcidevs = qemuGetPciHostDeviceList(hostdevs, nhostdevs)))
|
|
return -1;
|
|
|
|
/* We have to use 7 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.
|
|
*/
|
|
|
|
/* Loop 1: validate that non-managed device isn't in use, eg
|
|
* by checking that device is either un-bound, or bound
|
|
* to pci-stub.ko
|
|
*/
|
|
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
pciDevice *other;
|
|
|
|
if (!pciDeviceIsAssignable(dev, !driver->relaxedACS)) {
|
|
qemuReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("PCI device %s is not assignable"),
|
|
pciDeviceGetName(dev));
|
|
goto cleanup;
|
|
}
|
|
/* The device is in use by other active domain if
|
|
* the dev is in list driver->activePciHostdevs.
|
|
*/
|
|
if ((other = pciDeviceListFind(driver->activePciHostdevs, dev))) {
|
|
const char *other_name = pciDeviceGetUsedBy(other);
|
|
|
|
if (other_name)
|
|
qemuReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("PCI device %s is in use by domain %s"),
|
|
pciDeviceGetName(dev), other_name);
|
|
else
|
|
qemuReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("PCI device %s is already in use"),
|
|
pciDeviceGetName(dev));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Loop 2: detach managed devices */
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
if (pciDeviceGetManaged(dev) &&
|
|
pciDettachDevice(dev, driver->activePciHostdevs) < 0)
|
|
goto reattachdevs;
|
|
}
|
|
|
|
/* Loop 3: Now that all the PCI hostdevs have been detached, we
|
|
* can safely reset them */
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
if (pciResetDevice(dev, driver->activePciHostdevs, pcidevs) < 0)
|
|
goto reattachdevs;
|
|
}
|
|
|
|
/* Loop 4: Now mark all the devices as active */
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
if (pciDeviceListAdd(driver->activePciHostdevs, dev) < 0) {
|
|
pciFreeDevice(dev);
|
|
goto inactivedevs;
|
|
}
|
|
}
|
|
|
|
/* Loop 5: Now set the used_by_domain of the device in
|
|
* driver->activePciHostdevs as domain name.
|
|
*/
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev, *activeDev;
|
|
|
|
dev = pciDeviceListGet(pcidevs, i);
|
|
activeDev = pciDeviceListFind(driver->activePciHostdevs, dev);
|
|
|
|
pciDeviceSetUsedBy(activeDev, name);
|
|
}
|
|
|
|
/* Loop 6: Now set the original states for hostdev def */
|
|
for (i = 0; i < nhostdevs; i++) {
|
|
pciDevice *dev;
|
|
pciDevice *pcidev;
|
|
virDomainHostdevDefPtr hostdev = hostdevs[i];
|
|
|
|
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
|
|
continue;
|
|
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI)
|
|
continue;
|
|
|
|
dev = pciGetDevice(hostdev->source.subsys.u.pci.domain,
|
|
hostdev->source.subsys.u.pci.bus,
|
|
hostdev->source.subsys.u.pci.slot,
|
|
hostdev->source.subsys.u.pci.function);
|
|
|
|
/* original states "unbind_from_stub", "remove_slot",
|
|
* "reprobe" were already set by pciDettachDevice in
|
|
* loop 2.
|
|
*/
|
|
if ((pcidev = pciDeviceListFind(pcidevs, dev))) {
|
|
hostdev->origstates.states.pci.unbind_from_stub =
|
|
pciDeviceGetUnbindFromStub(pcidev);
|
|
hostdev->origstates.states.pci.remove_slot =
|
|
pciDeviceGetRemoveSlot(pcidev);
|
|
hostdev->origstates.states.pci.reprobe =
|
|
pciDeviceGetReprobe(pcidev);
|
|
}
|
|
|
|
pciFreeDevice(dev);
|
|
}
|
|
|
|
/* Loop 7: Now steal all the devices from pcidevs */
|
|
while (pciDeviceListCount(pcidevs) > 0) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, 0);
|
|
pciDeviceListSteal(pcidevs, dev);
|
|
}
|
|
|
|
ret = 0;
|
|
goto cleanup;
|
|
|
|
inactivedevs:
|
|
/* Only steal all the devices from driver->activePciHostdevs. We will
|
|
* free them in pciDeviceListFree().
|
|
*/
|
|
while (pciDeviceListCount(pcidevs) > 0) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, 0);
|
|
pciDeviceListSteal(driver->activePciHostdevs, dev);
|
|
}
|
|
|
|
reattachdevs:
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
pciReAttachDevice(dev, driver->activePciHostdevs);
|
|
}
|
|
|
|
cleanup:
|
|
pciDeviceListFree(pcidevs);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qemuPrepareHostPCIDevices(struct qemud_driver *driver,
|
|
virDomainDefPtr def)
|
|
{
|
|
return qemuPrepareHostdevPCIDevices(driver, def->name, def->hostdevs, def->nhostdevs);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuPrepareHostUSBDevices(struct qemud_driver *driver ATTRIBUTE_UNUSED,
|
|
virDomainDefPtr def)
|
|
{
|
|
int i;
|
|
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_USB)
|
|
continue;
|
|
|
|
/* Resolve a vendor/product to bus/device */
|
|
if (hostdev->source.subsys.u.usb.vendor) {
|
|
usbDevice *usb
|
|
= usbFindDevice(hostdev->source.subsys.u.usb.vendor,
|
|
hostdev->source.subsys.u.usb.product);
|
|
|
|
if (!usb)
|
|
return -1;
|
|
|
|
hostdev->source.subsys.u.usb.bus = usbDeviceGetBus(usb);
|
|
hostdev->source.subsys.u.usb.device = usbDeviceGetDevno(usb);
|
|
|
|
usbFreeDevice(usb);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuPrepareHostDevices(struct qemud_driver *driver,
|
|
virDomainDefPtr def)
|
|
{
|
|
if (!def->nhostdevs)
|
|
return 0;
|
|
|
|
if (qemuPrepareHostPCIDevices(driver, def) < 0)
|
|
return -1;
|
|
|
|
if (qemuPrepareHostUSBDevices(driver, def) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void qemuReattachPciDevice(pciDevice *dev, struct qemud_driver *driver)
|
|
{
|
|
int retries = 100;
|
|
|
|
if (!pciDeviceGetManaged(dev))
|
|
return;
|
|
|
|
while (pciWaitForDeviceCleanup(dev, "kvm_assigned_device")
|
|
&& retries) {
|
|
usleep(100*1000);
|
|
retries--;
|
|
}
|
|
|
|
if (pciReAttachDevice(dev, driver->activePciHostdevs) < 0) {
|
|
virErrorPtr err = virGetLastError();
|
|
VIR_ERROR(_("Failed to re-attach PCI device: %s"),
|
|
err ? err->message : _("unknown error"));
|
|
virResetError(err);
|
|
}
|
|
}
|
|
|
|
|
|
void qemuDomainReAttachHostdevDevices(struct qemud_driver *driver,
|
|
const char *name,
|
|
virDomainHostdevDefPtr *hostdevs,
|
|
int nhostdevs)
|
|
{
|
|
pciDeviceList *pcidevs;
|
|
int i;
|
|
|
|
if (!(pcidevs = qemuGetActivePciHostDeviceList(driver,
|
|
hostdevs,
|
|
nhostdevs))) {
|
|
virErrorPtr err = virGetLastError();
|
|
VIR_ERROR(_("Failed to allocate pciDeviceList: %s"),
|
|
err ? err->message : _("unknown error"));
|
|
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 < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
pciDevice *activeDev = NULL;
|
|
|
|
/* Never delete the dev from list driver->activePciHostdevs
|
|
* if it's used by other domain.
|
|
*/
|
|
activeDev = pciDeviceListFind(driver->activePciHostdevs, dev);
|
|
if (activeDev &&
|
|
STRNEQ_NULLABLE(name, pciDeviceGetUsedBy(activeDev))) {
|
|
pciDeviceListSteal(pcidevs, dev);
|
|
continue;
|
|
}
|
|
|
|
/* pciDeviceListFree() will take care of freeing the dev. */
|
|
pciDeviceListSteal(driver->activePciHostdevs, dev);
|
|
}
|
|
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
if (pciResetDevice(dev, driver->activePciHostdevs, pcidevs) < 0) {
|
|
virErrorPtr err = virGetLastError();
|
|
VIR_ERROR(_("Failed to reset PCI device: %s"),
|
|
err ? err->message : _("unknown error"));
|
|
virResetError(err);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < pciDeviceListCount(pcidevs); i++) {
|
|
pciDevice *dev = pciDeviceListGet(pcidevs, i);
|
|
qemuReattachPciDevice(dev, driver);
|
|
}
|
|
|
|
pciDeviceListFree(pcidevs);
|
|
}
|
|
|
|
|
|
void qemuDomainReAttachHostDevices(struct qemud_driver *driver,
|
|
virDomainDefPtr def)
|
|
{
|
|
if (!def->nhostdevs)
|
|
return;
|
|
|
|
qemuDomainReAttachHostdevDevices(driver, def->name, def->hostdevs, def->nhostdevs);
|
|
}
|