Hi, attached is some basic support for host device passthrough. It enables you to passthrough usb devices in qemu/kvm via: <devices> <hostdev type='usb' vendor='0204' product='6025'/> <hostdev type='usb' bus='001' device='007'/> </devices> I didn't implement unplug yet since this needs some modifications to qemu/kvm to be able to identify the correct device to unplug. Does this look reasonable? -- Guido
--- src/domain_conf.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/domain_conf.h | 33 +++++++++++++++ src/qemu_conf.c | 26 ++++++++++++ src/qemu_driver.c | 110 +++++++++++++++++++++++++++++++++++++------------ 4 files changed, 260 insertions(+), 28 deletions(-) diff --git a/src/domain_conf.c b/src/domain_conf.c index cd4a3da..d36caeb 100644 --- a/src/domain_conf.c +++ b/src/domain_conf.c @@ -125,6 +125,9 @@ VIR_ENUM_IMPL(virDomainGraphics, VIR_DOMAIN_GRAPHICS_TYPE_LAST, "sdl", "vnc") +VIR_ENUM_IMPL(virDomainHostdev, VIR_DOMAIN_HOSTDEV_TYPE_LAST, + "usb", + "pci") static void virDomainReportError(virConnectPtr conn, int code, const char *fmt, ...) @@ -314,6 +317,15 @@ void virDomainSoundDefFree(virDomainSoundDefPtr def) VIR_FREE(def); } +void virDomainHostdevDefFree(virDomainHostdevDefPtr def) +{ + if (!def) + return; + + virDomainHostdevDefFree(def->next); + VIR_FREE(def); +} + void virDomainDeviceDefFree(virDomainDeviceDefPtr def) { if (!def) @@ -332,6 +344,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def) case VIR_DOMAIN_DEVICE_SOUND: virDomainSoundDefFree(def->data.sound); break; + case VIR_DOMAIN_DEVICE_HOST: + virDomainHostdevDefFree(def->data.hostdev); + break; } VIR_FREE(def); @@ -350,7 +365,7 @@ void virDomainDefFree(virDomainDefPtr def) virDomainChrDefFree(def->parallels); virDomainChrDefFree(def->console); virDomainSoundDefFree(def->sounds); - + virDomainHostdevDefFree(def->hostdevs); VIR_FREE(def->os.type); VIR_FREE(def->os.arch); @@ -1297,6 +1312,88 @@ error: } +static virDomainHostdevDefPtr +virDomainHostdevDefParseXML(virConnectPtr conn, + const xmlNodePtr node) { + + virDomainHostdevDefPtr def; + char *type, *vendor; + + if (VIR_ALLOC(def) < 0) { + virDomainReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + type = virXMLPropString(node, "type"); + if (type) { + if ((def->type = virDomainHostdevTypeFromString(type)) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown host device type '%s'"), type); + goto error; + } else { + type = VIR_DOMAIN_HOSTDEV_TYPE_USB; + } + } + vendor = virXMLPropString(node, "vendor"); + if (vendor) { + char *product; + + def->usb.byModel = 1; + if (virStrToLong_ui(vendor, NULL, 16, &def->usb.vendor) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse vendor %s"), vendor); + VIR_FREE(vendor); + goto error; + } + VIR_FREE(vendor); + + product = virXMLPropString(node, "product"); + if (product) { + if (virStrToLong_ui(product, NULL, 16, &def->usb.product) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse product %s"), product); + VIR_FREE(product); + goto error; + } + VIR_FREE(product); + } + } else { + char *bus, *device; + + def->usb.byModel = 0; + bus = virXMLPropString(node, "bus"); + if (bus) { + if (virStrToLong_ui(bus, NULL, 16, &def->usb.bus) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse bus %s"), bus); + VIR_FREE(bus); + goto error; + } + VIR_FREE(bus); + } + + device = virXMLPropString(node, "device"); + if (device) { + if (virStrToLong_ui(device, NULL, 16, &def->usb.device) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse device %s"), device); + VIR_FREE(device); + goto error; + } + VIR_FREE(device); + } + } + +cleanup: + /* NOP */ + return def; + +error: + virDomainHostdevDefFree(def); + def = NULL; + goto cleanup; +} + + static int virDomainLifecycleParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, const char *xpath, @@ -1363,6 +1460,10 @@ virDomainDeviceDefPtr virDomainDeviceDefParse(virConnectPtr conn, dev->type = VIR_DOMAIN_DEVICE_SOUND; if (!(dev->data.sound = virDomainSoundDefParseXML(conn, node))) goto error; + } else if (xmlStrEqual(node->name, BAD_CAST "hostdev")) { + dev->type = VIR_DOMAIN_DEVICE_HOST; + if (!(dev->data.hostdev = virDomainHostdevDefParseXML(conn, node))) + goto error; } else { virDomainReportError(conn, VIR_ERR_XML_ERROR, "%s", _("unknown device type")); @@ -1829,6 +1930,22 @@ static virDomainDefPtr virDomainDefParseXML(virConnectPtr conn, } VIR_FREE(nodes); + /* analysis of the host devices */ + if ((n = virXPathNodeSet("./devices/hostdev", ctxt, &nodes)) < 0) { + virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot extract host devices")); + goto error; + } + for (i = 0 ; i < n ; i++) { + virDomainHostdevDefPtr hostdev = virDomainHostdevDefParseXML(conn, nodes[i]); + if (!hostdev) + goto error; + + hostdev->next = def->hostdevs; + def->hostdevs = hostdev; + } + VIR_FREE(nodes); + return def; error: diff --git a/src/domain_conf.h b/src/domain_conf.h index b438bc8..1aa5c39 100644 --- a/src/domain_conf.h +++ b/src/domain_conf.h @@ -257,7 +257,35 @@ struct _virDomainGraphicsDef { } data; }; +enum virDomainHostdevType { + VIR_DOMAIN_HOSTDEV_TYPE_USB, + VIR_DOMAIN_HOSTDEV_TYPE_PCI, + VIR_DOMAIN_HOSTDEV_TYPE_LAST +}; + +typedef struct _virDomainHostdevDef virDomainHostdevDef; +typedef virDomainHostdevDef *virDomainHostdevDefPtr; +struct _virDomainHostdevDef { + int type; + union { + struct { + int byModel; + union { + unsigned vendor; + unsigned bus; + }; + union { + unsigned product; + unsigned device; + }; + } usb; + struct { + /* TBD */ + } pci; + }; + virDomainHostdevDefPtr next; +}; /* Flags for the 'type' field in next struct */ enum virDomainDeviceType { @@ -265,6 +293,7 @@ enum virDomainDeviceType { VIR_DOMAIN_DEVICE_NET, VIR_DOMAIN_DEVICE_INPUT, VIR_DOMAIN_DEVICE_SOUND, + VIR_DOMAIN_DEVICE_HOST, }; typedef struct _virDomainDeviceDef virDomainDeviceDef; @@ -276,6 +305,7 @@ struct _virDomainDeviceDef { virDomainNetDefPtr net; virDomainInputDefPtr input; virDomainSoundDefPtr sound; + virDomainHostdevDefPtr hostdev; } data; }; @@ -360,6 +390,7 @@ struct _virDomainDef { virDomainNetDefPtr nets; virDomainInputDefPtr inputs; virDomainSoundDefPtr sounds; + virDomainHostdevDefPtr hostdevs; virDomainChrDefPtr serials; virDomainChrDefPtr parallels; virDomainChrDefPtr console; @@ -414,6 +445,7 @@ void virDomainDiskDefFree(virDomainDiskDefPtr def); void virDomainNetDefFree(virDomainNetDefPtr def); void virDomainChrDefFree(virDomainChrDefPtr def); void virDomainSoundDefFree(virDomainSoundDefPtr def); +void virDomainHostdevDefFree(virDomainHostdevDefPtr def); void virDomainDeviceDefFree(virDomainDeviceDefPtr def); void virDomainDefFree(virDomainDefPtr vm); void virDomainObjFree(virDomainObjPtr vm); @@ -484,6 +516,7 @@ VIR_ENUM_DECL(virDomainDiskBus) VIR_ENUM_DECL(virDomainNet) VIR_ENUM_DECL(virDomainChr) VIR_ENUM_DECL(virDomainSoundModel) +VIR_ENUM_DECL(virDomainHostdev) VIR_ENUM_DECL(virDomainInput) VIR_ENUM_DECL(virDomainInputBus) VIR_ENUM_DECL(virDomainGraphics) diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 9cd8c1e..7678ac5 100644 --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -723,6 +723,7 @@ int qemudBuildCommandLine(virConnectPtr conn, virDomainNetDefPtr net = vm->def->nets; virDomainInputDefPtr input = vm->def->inputs; virDomainSoundDefPtr sound = vm->def->sounds; + virDomainHostdevDefPtr hostdev = vm->def->hostdevs; virDomainChrDefPtr serial = vm->def->serials; virDomainChrDefPtr parallel = vm->def->parallels; struct utsname ut; @@ -1154,6 +1155,31 @@ int qemudBuildCommandLine(virConnectPtr conn, ADD_ARG(modstr); } + /* Add host passthrough hardware */ + while (hostdev) { + int ret; + char* usbdev; + + if (hostdev->type == VIR_DOMAIN_HOSTDEV_TYPE_USB) { + if(hostdev->usb.vendor) { + ret = asprintf(&usbdev, "host:%.4x:%.4x", + hostdev->usb.vendor, hostdev->usb.product); + + } else { + ret = asprintf(&usbdev, "host:%.3x.%.3x", + hostdev->usb.bus, hostdev->usb.device); + } + if (ret < 0) { + usbdev = NULL; + goto error; + } + ADD_ARG_LIT("-usbdevice"); + ADD_ARG_LIT(usbdev); + VIR_FREE(usbdev); + } + hostdev = hostdev->next; + } + if (migrateFrom) { ADD_ARG_LIT("-incoming"); ADD_ARG_LIT(migrateFrom); diff --git a/src/qemu_driver.c b/src/qemu_driver.c index d0c8184..81bde4e 100644 --- a/src/qemu_driver.c +++ b/src/qemu_driver.c @@ -2941,12 +2941,84 @@ static int qemudDomainChangeCDROM(virDomainPtr dom, return 0; } +static int qemudDomainAttachCdromDevice(virDomainPtr dom, + virDomainDeviceDefPtr dev) +{ + struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid); + virDomainDiskDefPtr disk; + + disk = vm->def->disks; + while (disk) { + if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM && + STREQ(disk->dst, dev->data.disk->dst)) + break; + disk = disk->next; + } + + if (!disk) { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("CDROM not attached, cannot change media")); + return -1; + } + + if (qemudDomainChangeCDROM(dom, vm, disk, dev->data.disk) < 0) { + return -1; + } + return 0; +} + +static int qemudDomainAttachHostDevice(virDomainPtr dom, virDomainDeviceDefPtr dev) +{ + struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid); + int ret; + char *cmd, *reply; + + if (dev->data.hostdev->usb.byModel) { + ret = asprintf(&cmd, "usb_add host:%.4x:%.4x", + dev->data.hostdev->usb.vendor, + dev->data.hostdev->usb.product); + } else { + ret = asprintf(&cmd, "usb_add host:%.3x.%.3x", + dev->data.hostdev->usb.bus, + dev->data.hostdev->usb.device); + } + if (ret == -1) { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("out of memory")); + return -1; + } + + if (qemudMonitorCommand(driver, vm, cmd, &reply) < 0) { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("cannot attach usb device")); + VIR_FREE(cmd); + return -1; + } + + DEBUG ("attach_usb reply: %s", reply); + /* If the command failed qemu prints: + * Could not add ... */ + if (strstr(reply, "Could not add ")) { + qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", + _("adding usb device failed")); + VIR_FREE(reply); + VIR_FREE(cmd); + return -1; + } + VIR_FREE(reply); + VIR_FREE(cmd); + return 0; +} + static int qemudDomainAttachDevice(virDomainPtr dom, const char *xml) { struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData; virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid); virDomainDeviceDefPtr dev; - virDomainDiskDefPtr disk; + int ret = 0; if (!vm) { qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, @@ -2965,36 +3037,20 @@ static int qemudDomainAttachDevice(virDomainPtr dom, return -1; } - if (dev->type != VIR_DOMAIN_DEVICE_DISK || - dev->data.disk->device != VIR_DOMAIN_DISK_DEVICE_CDROM) { - qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, - "%s", _("only CDROM disk devices can be attached")); - VIR_FREE(dev); - return -1; - } - - disk = vm->def->disks; - while (disk) { - if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM && - STREQ(disk->dst, dev->data.disk->dst)) - break; - disk = disk->next; - } - - if (!disk) { + if (dev->type == VIR_DOMAIN_DEVICE_DISK && + dev->data.disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) { + ret = qemudDomainAttachCdromDevice(dom, dev); + } else if (dev->type == VIR_DOMAIN_DEVICE_HOST && + dev->data.hostdev->type == VIR_DOMAIN_HOSTDEV_TYPE_USB) { + ret = qemudDomainAttachHostDevice(dom, dev); + } else { qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, - "%s", _("CDROM not attached, cannot change media")); - VIR_FREE(dev); - return -1; - } - - if (qemudDomainChangeCDROM(dom, vm, disk, dev->data.disk) < 0) { - VIR_FREE(dev); - return -1; + "%s", _("this devicetype cannnot be attached")); + ret = -1; } VIR_FREE(dev); - return 0; + return ret; } static int qemudDomainGetAutostart(virDomainPtr dom, -- 1.5.6.3
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list