In order to avoid situation where a USB device is
in use by two domains, we must keep a list of already
attached devices like we do for PCI.
---
src/qemu/qemu_conf.h | 2 +
src/qemu/qemu_driver.c | 4 +
src/qemu/qemu_hostdev.c | 92 ++++++++++++++++++++++++++++--
src/qemu/qemu_hostdev.h | 4 +
src/qemu/qemu_hotplug.c | 17 +++++-
src/util/hostusb.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++
src/util/hostusb.h | 18 ++++++
7 files changed, 269 insertions(+), 8 deletions(-)
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index f5a0f60..d8d7915 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -36,6 +36,7 @@
# include "security/security_manager.h"
# include "cgroup.h"
# include "pci.h"
+# include "hostusb.h"
# include "cpu_conf.h"
# include "driver.h"
# include "bitmap.h"
@@ -125,6 +126,7 @@ struct qemud_driver {
bool autoStartBypassCache;
pciDeviceList *activePciHostdevs;
+ usbDeviceList *activeUsbHostdevs;
virBitmapPtr reservedVNCPorts;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index c908135..eeeb935 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -583,6 +583,9 @@ qemudStartup(int privileged) {
if ((qemu_driver->activePciHostdevs = pciDeviceListNew()) == NULL)
goto error;
+ if ((qemu_driver->activeUsbHostdevs = usbDeviceListNew()) == NULL)
+ goto error;
+
if (privileged) {
if (chown(qemu_driver->libDir, qemu_driver->user, qemu_driver->group)
< 0) {
virReportSystemError(errno,
@@ -773,6 +776,7 @@ qemudShutdown(void) {
qemuDriverLock(qemu_driver);
pciDeviceListFree(qemu_driver->activePciHostdevs);
+ usbDeviceListFree(qemu_driver->activeUsbHostdevs);
virCapabilitiesFree(qemu_driver->caps);
virDomainObjListDeinit(&qemu_driver->domains);
diff --git a/src/qemu/qemu_hostdev.c b/src/qemu/qemu_hostdev.c
index 60401f0..c7adb1d 100644
--- a/src/qemu/qemu_hostdev.c
+++ b/src/qemu/qemu_hostdev.c
@@ -314,13 +314,30 @@ qemuPrepareHostPCIDevices(struct qemud_driver *driver,
}
-static int
-qemuPrepareHostUSBDevices(struct qemud_driver *driver ATTRIBUTE_UNUSED,
- virDomainDefPtr def)
+int
+qemuPrepareHostdevUSBDevices(struct qemud_driver *driver,
+ const char *name,
+ virDomainHostdevDefPtr *hostdevs,
+ int nhostdevs)
{
+ int ret = -1;
int i;
- for (i = 0 ; i < def->nhostdevs ; i++) {
- virDomainHostdevDefPtr hostdev = def->hostdevs[i];
+ usbDeviceList *list;
+ usbDevice *tmp;
+
+ /* To prevent situation where USB device is assigned to two domains
+ * we need to keep a list of currently assigned USB devices.
+ * This is done in several loops which cannot be joined into one big
+ * loop. See qemuPrepareHostdevPCIDevices()
+ */
+ if (!(list = usbDeviceListNew()))
+ goto cleanup;
+
+ /* Loop 1: build temporary list and validate no usb device
+ * is already taken
+ */
+ for (i = 0 ; i < nhostdevs ; i++) {
+ virDomainHostdevDefPtr hostdev = hostdevs[i];
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
continue;
@@ -339,13 +356,74 @@ qemuPrepareHostUSBDevices(struct qemud_driver *driver
ATTRIBUTE_UNUSED,
hostdev->source.subsys.u.usb.bus = usbDeviceGetBus(usb);
hostdev->source.subsys.u.usb.device = usbDeviceGetDevno(usb);
- usbFreeDevice(usb);
+ if ((tmp = usbDeviceListFind(driver->activeUsbHostdevs, usb))) {
+ const char *other_name = usbDeviceGetUsedBy(tmp);
+
+ if (other_name)
+ qemuReportError(VIR_ERR_OPERATION_INVALID,
+ _("USB device %s is in use by domain %s"),
+ usbDeviceGetName(tmp), other_name);
+ else
+ qemuReportError(VIR_ERR_OPERATION_INVALID,
+ _("USB device %s is already in use"),
+ usbDeviceGetName(tmp));
+ usbFreeDevice(usb);
+ goto cleanup;
+ }
+
+ if (usbDeviceListAdd(list, usb) < 0) {
+ usbFreeDevice(usb);
+ goto cleanup;
+ }
+
}
}
- return 0;
+ /* Loop 2: Mark devices in temporary list as used by @name
+ * and add them do driver list. However, if something goes
+ * wrong, perform rollback.
+ */
+ for (i = 0; i < usbDeviceListCount(list); i++) {
+ tmp = usbDeviceListGet(list, i);
+ usbDeviceSetUsedBy(tmp, name);
+ if (usbDeviceListAdd(driver->activeUsbHostdevs, tmp) < 0) {
+ usbFreeDevice(tmp);
+ goto inactivedevs;
+ }
+ }
+
+ /* Loop 3: Temporary list was successfully merged with
+ * driver list, so steal all items to avoid freeing them
+ * in cleanup label.
+ */
+ while (usbDeviceListCount(list) > 0) {
+ tmp = usbDeviceListGet(list, 0);
+ usbDeviceListSteal(list, tmp);
+ }
+
+ ret = 0;
+ goto cleanup;
+
+inactivedevs:
+ /* Steal devices from driver->activeUsbHostdevs.
+ * We will free them later.
+ */
+ for (i = 0; i < usbDeviceListCount(list); i++) {
+ tmp = usbDeviceListGet(list, i);
+ usbDeviceListSteal(driver->activeUsbHostdevs, tmp);
+ }
+
+cleanup:
+ usbDeviceListFree(list);
+ return ret;
}
+static int
+qemuPrepareHostUSBDevices(struct qemud_driver *driver,
+ virDomainDefPtr def)
+{
+ return qemuPrepareHostdevUSBDevices(driver, def->name, def->hostdevs,
def->nhostdevs);
+}
int qemuPrepareHostDevices(struct qemud_driver *driver,
virDomainDefPtr def)
diff --git a/src/qemu/qemu_hostdev.h b/src/qemu/qemu_hostdev.h
index 07d7de2..d852f5b 100644
--- a/src/qemu/qemu_hostdev.h
+++ b/src/qemu/qemu_hostdev.h
@@ -33,6 +33,10 @@ int qemuPrepareHostdevPCIDevices(struct qemud_driver *driver,
const char *name,
virDomainHostdevDefPtr *hostdevs,
int nhostdevs);
+int qemuPrepareHostdevUSBDevices(struct qemud_driver *driver,
+ const char *name,
+ virDomainHostdevDefPtr *hostdevs,
+ int nhostdevs);
int qemuPrepareHostDevices(struct qemud_driver *driver,
virDomainDefPtr def);
void qemuReattachPciDevice(pciDevice *dev, struct qemud_driver *driver);
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 4067bb0..f3597a1 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -1097,6 +1097,9 @@ int qemuDomainAttachHostDevice(struct qemud_driver *driver,
/* Resolve USB product/vendor to bus/device */
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB &&
hostdev->source.subsys.u.usb.vendor) {
+ if (qemuPrepareHostdevUSBDevices(driver, vm->def->name, &hostdev, 1)
< 0)
+ goto error;
+
usbDevice *usb
= usbFindDevice(hostdev->source.subsys.u.usb.vendor,
hostdev->source.subsys.u.usb.product);
@@ -2068,6 +2071,7 @@ qemuDomainDetachHostUsbDevice(struct qemud_driver *driver,
{
virDomainHostdevDefPtr detach = NULL;
qemuDomainObjPrivatePtr priv = vm->privateData;
+ usbDevice *usb;
int i, ret;
for (i = 0 ; i < vm->def->nhostdevs ; i++) {
@@ -2123,6 +2127,17 @@ qemuDomainDetachHostUsbDevice(struct qemud_driver *driver,
if (ret < 0)
return -1;
+ usb = usbGetDevice(detach->source.subsys.u.usb.bus,
+ detach->source.subsys.u.usb.device);
+ if (usb) {
+ usbDeviceListDel(driver->activeUsbHostdevs, usb);
+ usbFreeDevice(usb);
+ } else {
+ VIR_WARN("Unable to find device %03d.%03d in list of used USB
devices",
+ detach->source.subsys.u.usb.bus,
+ detach->source.subsys.u.usb.device);
+ }
+
if (vm->def->nhostdevs > 1) {
memmove(vm->def->hostdevs + i,
vm->def->hostdevs + i + 1,
@@ -2162,7 +2177,7 @@ int qemuDomainDetachHostDevice(struct qemud_driver *driver,
switch (hostdev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
ret = qemuDomainDetachHostPciDevice(driver, vm, dev, &detach);
- break;
+ break;
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
ret = qemuDomainDetachHostUsbDevice(driver, vm, dev, &detach);
break;
diff --git a/src/util/hostusb.c b/src/util/hostusb.c
index 1669e2f..92f52a2 100644
--- a/src/util/hostusb.c
+++ b/src/util/hostusb.c
@@ -49,6 +49,12 @@ struct _usbDevice {
char name[USB_ADDR_LEN]; /* domain:bus:slot.function */
char id[USB_ID_LEN]; /* product vendor */
char *path;
+ const char *used_by; /* name of the domain using this dev */
+};
+
+struct _usbDeviceList {
+ unsigned int count;
+ usbDevice **devs;
};
/* For virReportOOMError() and virReportSystemError() */
@@ -225,6 +231,22 @@ usbFreeDevice(usbDevice *dev)
}
+void usbDeviceSetUsedBy(usbDevice *dev,
+ const char *name)
+{
+ dev->used_by = name;
+}
+
+const char * usbDeviceGetUsedBy(usbDevice *dev)
+{
+ return dev->used_by;
+}
+
+const char *usbDeviceGetName(usbDevice *dev)
+{
+ return dev->name;
+}
+
unsigned usbDeviceGetBus(usbDevice *dev)
{
return dev->bus;
@@ -243,3 +265,121 @@ int usbDeviceFileIterate(usbDevice *dev,
{
return (actor)(dev, dev->path, opaque);
}
+
+usbDeviceList *
+usbDeviceListNew(void)
+{
+ usbDeviceList *list;
+
+ if (VIR_ALLOC(list) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ return list;
+}
+
+void
+usbDeviceListFree(usbDeviceList *list)
+{
+ int i;
+
+ if (!list)
+ return;
+
+ for (i = 0; i < list->count; i++)
+ usbFreeDevice(list->devs[i]);
+
+ VIR_FREE(list->devs);
+ VIR_FREE(list);
+}
+
+int
+usbDeviceListAdd(usbDeviceList *list,
+ usbDevice *dev)
+{
+ if (usbDeviceListFind(list, dev)) {
+ usbReportError(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();
+ return -1;
+ }
+
+ list->devs[list->count++] = dev;
+
+ return 0;
+}
+
+usbDevice *
+usbDeviceListGet(usbDeviceList *list,
+ int idx)
+{
+ if (idx >= list->count ||
+ idx < 0)
+ return NULL;
+
+ return list->devs[idx];
+}
+
+int
+usbDeviceListCount(usbDeviceList *list)
+{
+ return list->count;
+}
+
+usbDevice *
+usbDeviceListSteal(usbDeviceList *list,
+ usbDevice *dev)
+{
+ usbDevice *ret = NULL;
+ int i;
+
+ for (i = 0; i < list->count; i++) {
+ if (list->devs[i]->bus != dev->bus ||
+ list->devs[i]->dev != dev->dev)
+ continue;
+
+ ret = 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;
+ }
+ return ret;
+}
+
+void
+usbDeviceListDel(usbDeviceList *list,
+ usbDevice *dev)
+{
+ usbDevice *ret = usbDeviceListSteal(list, dev);
+ if (ret)
+ usbFreeDevice(ret);
+}
+
+usbDevice *
+usbDeviceListFind(usbDeviceList *list,
+ usbDevice *dev)
+{
+ int i;
+
+ for (i = 0; i < list->count; i++) {
+ if (list->devs[i]->bus == dev->bus &&
+ list->devs[i]->dev == dev->dev)
+ return list->devs[i];
+ }
+
+ return NULL;
+}
diff --git a/src/util/hostusb.h b/src/util/hostusb.h
index f4a13ca..afaa32f 100644
--- a/src/util/hostusb.h
+++ b/src/util/hostusb.h
@@ -17,6 +17,7 @@
*
* Authors:
* Daniel P. Berrange <berrange(a)redhat.com>
+ * Michal Privoznik <mprivozn(a)redhat.com>
*/
#ifndef __VIR_USB_H__
@@ -25,12 +26,16 @@
# include "internal.h"
typedef struct _usbDevice usbDevice;
+typedef struct _usbDeviceList usbDeviceList;
usbDevice *usbGetDevice(unsigned bus,
unsigned devno);
usbDevice *usbFindDevice(unsigned vendor,
unsigned product);
void usbFreeDevice (usbDevice *dev);
+void usbDeviceSetUsedBy(usbDevice *dev, const char *name);
+const char *usbDeviceGetUsedBy(usbDevice *dev);
+const char *usbDeviceGetName(usbDevice *dev);
unsigned usbDeviceGetBus(usbDevice *dev);
unsigned usbDeviceGetDevno(usbDevice *dev);
@@ -49,5 +54,18 @@ int usbDeviceFileIterate(usbDevice *dev,
usbDeviceFileActor actor,
void *opaque);
+usbDeviceList *usbDeviceListNew(void);
+void usbDeviceListFree(usbDeviceList *list);
+int usbDeviceListAdd(usbDeviceList *list,
+ usbDevice *dev);
+usbDevice * usbDeviceListGet(usbDeviceList *list,
+ int idx);
+int usbDeviceListCount(usbDeviceList *list);
+usbDevice * usbDeviceListSteal(usbDeviceList *list,
+ usbDevice *dev);
+void usbDeviceListDel(usbDeviceList *list,
+ usbDevice *dev);
+usbDevice * usbDeviceListFind(usbDeviceList *list,
+ usbDevice *dev);
#endif /* __VIR_USB_H__ */
--
1.7.3.4