[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[libvirt] [PATCH/RFC]: hostdev passthrough support



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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]