mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-03-20 07:59:00 +00:00
qemu: Keep list of USB devices attached to domains
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.
This commit is contained in:
parent
d145fe3bb3
commit
8a34f822e6
@ -1045,6 +1045,17 @@ virThreadSelfID;
|
||||
usbDeviceFileIterate;
|
||||
usbDeviceGetBus;
|
||||
usbDeviceGetDevno;
|
||||
usbDeviceGetName;
|
||||
usbDeviceGetUsedBy;
|
||||
usbDeviceListAdd;
|
||||
usbDeviceListCount;
|
||||
usbDeviceListDel;
|
||||
usbDeviceListFind;
|
||||
usbDeviceListFree;
|
||||
usbDeviceListGet;
|
||||
usbDeviceListNew;
|
||||
usbDeviceListSteal;
|
||||
usbDeviceSetUsedBy;
|
||||
usbFindDevice;
|
||||
usbFreeDevice;
|
||||
usbGetDevice;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
*
|
||||
* Authors:
|
||||
* Daniel P. Berrange <berrange@redhat.com>
|
||||
* Michal Privoznik <mprivozn@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__ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user