Attaching a host PCI device to a qemu guest is done with a
straightforward 'pci_add 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.
* src/qemu_driver.c: add qemudDomainAttachHostPciDevice() and
qemudDomainDetachHostPciDevice()
* src/domain_conf.h: add somewhere to store the guest PCI address
* src/domain_conf.c: handle formatting and parsing the guest PCI
address
---
src/domain_conf.c | 33 +++++++-
src/domain_conf.h | 13 +++
src/qemu_driver.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 252 insertions(+), 4 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, " <state");
+ if (virHostdevHasValidGuestAddr(def))
+ virBufferVSprintf(buf, " devaddr='%.4x:%.2x:%.2x'",
+ def->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, " </source>\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, " </devices>\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/qemu_driver.c b/src/qemu_driver.c
index f181f27..041e3da 100644
--- a/src/qemu_driver.c
+++ b/src/qemu_driver.c
@@ -5148,6 +5148,80 @@ cleanup:
return -1;
}
+static int qemudDomainAttachHostPciDevice(virConnectPtr conn,
+ virDomainObjPtr vm,
+ virDomainDeviceDefPtr dev)
+{
+ virDomainHostdevDefPtr hostdev = dev->data.hostdev;
+ char *cmd, *reply;
+ unsigned domain, bus, slot;
+
+ if (VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs+1) < 0) {
+ virReportOOMError(conn);
+ return -1;
+ }
+
+ if (hostdev->managed) {
+ pciDevice *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 (pciDettachDevice(conn, pci) < 0 ||
+ pciResetDevice(conn, pci) < 0) {
+ pciFreeDevice(conn, pci);
+ return -1;
+ }
+
+ pciFreeDevice(conn, pci);
+ }
+
+ if (virAsprintf(&cmd, "pci_add 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);
+ return -1;
+ }
+
+ if (qemudMonitorCommand(vm, cmd, &reply) < 0) {
+ qemudReportError(conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+ "%s", _("cannot attach host pci device"));
+ VIR_FREE(cmd);
+ return -1;
+ }
+
+ 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"));
+ VIR_FREE(cmd);
+ VIR_FREE(reply);
+ return -1;
+ }
+
+ if (qemudParsePciAddReply(vm, reply, &domain, &bus, &slot) < 0) {
+ qemudReportError(conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+ _("parsing pci_add reply failed: %s"), reply);
+ VIR_FREE(cmd);
+ VIR_FREE(reply);
+ return -1;
+ }
+
+ 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;
+}
+
static int qemudDomainAttachHostUsbDevice(virConnectPtr conn,
virDomainObjPtr vm,
virDomainDeviceDefPtr dev)
@@ -5218,6 +5292,8 @@ static int qemudDomainAttachHostDevice(virConnectPtr conn,
return -1;
switch (hostdev->source.subsys.type) {
+ case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
+ return qemudDomainAttachHostPciDevice(conn, vm, dev);
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
return qemudDomainAttachHostUsbDevice(conn, vm, dev);
default:
@@ -5545,6 +5621,138 @@ cleanup:
return ret;
}
+static int qemudDomainDetachHostPciDevice(virConnectPtr conn,
+ virDomainObjPtr vm,
+ virDomainDeviceDefPtr dev)
+{
+ virDomainHostdevDefPtr detach;
+ char *cmd, *reply;
+ int i, ret;
+
+ 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;
+
+ if (detach->managed) {
+ pciDevice *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 || pciReAttachDevice(conn, pci) < 0)
+ ret = -1;
+ if (pci)
+ pciFreeDevice(conn, pci);
+ }
+
+ if (vm->def->nhostdevs > 1) {
+ vm->def->hostdevs[i] =
vm->def->hostdevs[--vm->def->nhostdevs];
+ if (VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs) < 0) {
+ virReportOOMError(conn);
+ ret = -1;
+ }
+ } else {
+ VIR_FREE(vm->def->hostdevs[0]);
+ vm->def->nhostdevs = 0;
+ }
+
+ 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, vm, dev);
+ 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;
@@ -5585,6 +5793,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