[libvirt] [PATCH/RFC]: hostdev passthrough support take #2

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

 



Hi,
attached is a second version. Changes are:

* s/bus/subsystem/
* support hexadecimal and decimal attributes
* introduce device and source elements. 

I decided to not drop vendor and product id into their own elements
since the structure would then become very nested for no good reason.
Some examples:

    <hostdev mode="subsys" type='usb'>
      <source>
        <device vendor="0x0204" product="0x6025"/>
      </source>
    </hostdev>

    <hostdev mode="subsys" type='usb'>
      <source>
        <address bus="001" device="003"/>
      </source>
    </hostdev>

Support for <target> will be coming once I got around to adjust
qemu/kvm.
Cheers,
 -- Guido
>From 264b731042da15601d0840149557aceae5c7d96e Mon Sep 17 00:00:00 2001
From: Guido Guenther <agx@xxxxxxxxxxx>
Date: Fri, 25 Jul 2008 15:18:16 -0400
Subject: [PATCH] host device passthrough

---
 src/domain_conf.c |  208 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/domain_conf.h |   51 +++++++++++++
 src/qemu_conf.c   |   29 ++++++++
 src/qemu_driver.c |  111 +++++++++++++++++++++-------
 4 files changed, 371 insertions(+), 28 deletions(-)

diff --git a/src/domain_conf.c b/src/domain_conf.c
index 4998a7d..ab2ee9f 100644
--- a/src/domain_conf.c
+++ b/src/domain_conf.c
@@ -131,6 +131,13 @@ VIR_ENUM_IMPL(virDomainGraphics, VIR_DOMAIN_GRAPHICS_TYPE_LAST,
               "sdl",
               "vnc")
 
+VIR_ENUM_IMPL(virDomainHostdevMode, VIR_DOMAIN_HOSTDEV_MODE_LAST,
+              "subsys",
+              "capabilities")
+
+VIR_ENUM_IMPL(virDomainHostdevSubsys, VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST,
+              "usb",
+              "pci")
 
 static void virDomainReportError(virConnectPtr conn,
                                  int code, const char *fmt, ...)
@@ -332,6 +339,16 @@ void virDomainSoundDefFree(virDomainSoundDefPtr def)
     VIR_FREE(def);
 }
 
+void virDomainHostdevDefFree(virDomainHostdevDefPtr def)
+{
+    if (!def)
+        return;
+
+    VIR_FREE(def->target);
+    virDomainHostdevDefFree(def->next);
+    VIR_FREE(def);
+}
+
 void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
 {
     if (!def)
@@ -350,6 +367,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
     case VIR_DOMAIN_DEVICE_SOUND:
         virDomainSoundDefFree(def->data.sound);
         break;
+    case VIR_DOMAIN_DEVICE_HOSTDEV:
+        virDomainHostdevDefFree(def->data.hostdev);
+        break;
     }
 
     VIR_FREE(def);
@@ -369,7 +389,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);
@@ -1400,6 +1420,172 @@ error:
     goto cleanup;
 }
 
+static int
+virDomainHostdevSubsysUsbDefParseXML(virConnectPtr conn,
+                                     const xmlNodePtr node,
+				     virDomainHostdevDefPtr def) {
+
+    int ret = -1;
+    xmlNodePtr cur;
+
+    def->source.subsys.usb.vendor = 0;
+    def->source.subsys.usb.product = 0;
+    def->source.subsys.usb.bus = 0;
+    def->source.subsys.usb.device = 0;
+
+    cur = node->children;
+    while (cur != NULL) {
+        if (cur->type == XML_ELEMENT_NODE) {
+            if (xmlStrEqual(cur->name, BAD_CAST "device")) {
+                char *vendor, *product;
+
+                vendor = virXMLPropString(cur, "vendor");
+                if (vendor) {
+                    if (virStrToLong_ui(vendor, NULL, 0, 
+                                        &def->source.subsys.usb.vendor) < 0) {
+                        virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                 _("cannot parse vendor %s"), vendor);
+                        VIR_FREE(vendor);
+                        goto out;
+                    }
+                    VIR_FREE(vendor);
+                } else {
+                    virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                         "%s", _("usb device needs vendor"));
+                    goto out;
+                }
+
+                product = virXMLPropString(cur, "product");
+                if (product) {
+                    if (virStrToLong_ui(product, NULL, 0, 
+                                        &def->source.subsys.usb.product) < 0) {
+                        virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                            _("cannot parse product %s"), product);
+                        VIR_FREE(product);
+                        goto out;
+                    }
+                    VIR_FREE(product);
+                } else {
+                    virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                         "%s", _("usb device needs product"));
+                    goto out;
+                }
+
+            } else if (xmlStrEqual(cur->name, BAD_CAST "address")) {
+                char *bus, *device;
+
+                bus = virXMLPropString(cur, "bus");
+                if (bus) {
+                    if (virStrToLong_ui(bus, NULL, 0, 
+                                        &def->source.subsys.usb.bus) < 0) {
+                        virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                             _("cannot parse bus %s"), bus);
+                        VIR_FREE(bus);
+                        goto out;
+                    }
+                    VIR_FREE(bus);
+                } else {
+                    virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                         "%s", _("usb address needs bus id"));
+                    goto out;
+                }
+
+                device = virXMLPropString(cur, "device");
+                if (device) {
+                    if (virStrToLong_ui(device, NULL, 0, 
+                                        &def->source.subsys.usb.device) < 0)  {
+                        virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                             _("cannot parse device %s"), 
+                                             device);
+                        VIR_FREE(device);
+                        goto out;
+                    }
+                    VIR_FREE(device);
+                } else {
+                    virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                         "%s", _("usb address needs device id"));
+                    goto out;
+                }
+	    } else {
+                virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                     _("unknown usb source type '%s'"), cur->name);
+                goto out;
+            }
+        }
+        cur = cur->next;
+    }
+    ret = 0;
+out:
+    return ret;
+}
+
+
+static virDomainHostdevDefPtr
+virDomainHostdevDefParseXML(virConnectPtr conn,
+                            const xmlNodePtr node) {
+
+    xmlNodePtr cur;
+    virDomainHostdevDefPtr def;
+    char *mode, *type = NULL;
+
+    if (VIR_ALLOC(def) < 0) {
+        virDomainReportError(conn, VIR_ERR_NO_MEMORY, NULL);
+        return NULL;
+    }
+    def->target = NULL;
+
+    mode = virXMLPropString(node, "mode");
+    if (mode) {
+        if ((def->mode=virDomainHostdevModeTypeFromString(mode)) < 0) {
+             virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                  _("unknown hostdev mode '%s'"), mode);
+            goto error;
+        }
+    } else {
+        def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
+    }
+
+    type = virXMLPropString(node, "type");
+    if (type) {
+        if ((def->source.subsys.type = virDomainHostdevSubsysTypeFromString(type)) < 0) {
+            virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                 _("unknown host device type '%s'"), type);
+            goto error;
+        }
+    } else {
+        virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                             "%s", _("missing type in hostdev"));
+        goto error;
+    }
+
+    cur = node->children;
+    while (cur != NULL) {
+        if (cur->type == XML_ELEMENT_NODE) {
+            if (xmlStrEqual(cur->name, BAD_CAST "source")) {
+                if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
+                    def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
+                        if (virDomainHostdevSubsysUsbDefParseXML(conn, cur, def) < 0)
+                            goto error;
+                }
+            } else {
+                virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                                     _("uknown node %s"), cur->name);
+            }
+        }
+        cur = cur->next;
+    }
+
+cleanup:
+    VIR_FREE(type);
+    VIR_FREE(mode);
+    return def;
+
+error:
+    virDomainHostdevDefFree(def);
+    def = NULL;
+    goto cleanup;
+}
+
 
 static int virDomainLifecycleParseXML(virConnectPtr conn,
                                       xmlXPathContextPtr ctxt,
@@ -1471,6 +1657,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_HOSTDEV;
+        if (!(dev->data.hostdev = virDomainHostdevDefParseXML(conn, node)))
+            goto error;
     } else {
         virDomainReportError(conn, VIR_ERR_XML_ERROR,
                              "%s", _("unknown device type"));
@@ -1965,6 +2155,22 @@ static virDomainDefPtr virDomainDefParseXML(virConnectPtr conn,
     }
     VIR_FREE(nodes);
 
+    /* analysis of the host devices */
+    if ((n = virXPathNodeSet(conn, "./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 527dc71..8a9d1db 100644
--- a/src/domain_conf.h
+++ b/src/domain_conf.h
@@ -279,7 +279,52 @@ struct _virDomainGraphicsDef {
     } data;
 };
 
+enum virDomainHostdevMode {
+    VIR_DOMAIN_HOSTDEV_MODE_SUBSYS,
+    VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES,
 
+    VIR_DOMAIN_HOSTDEV_MODE_LAST,
+};
+
+enum virDomainHostdevSubsysType {
+    VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB,
+    VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI,
+
+    VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST
+};
+
+typedef struct _virDomainHostdevDef virDomainHostdevDef;
+typedef virDomainHostdevDef *virDomainHostdevDefPtr;
+struct _virDomainHostdevDef {
+    int mode; /* enum virDomainHostdevMode */
+    union {
+        struct {
+            int type; /* enum virDomainHostdevBusType */
+            union {
+                struct {
+                    unsigned bus;
+                    unsigned device;
+
+                    unsigned vendor;
+                    unsigned product;
+                } usb;
+                struct {
+                     unsigned domain;
+                     unsigned bus;
+                     unsigned slot;
+                     unsigned function;
+                } pci;
+            };
+        } subsys;
+        struct {
+            /* TBD: struct capabilities see:
+             * https://www.redhat.com/archives/libvir-list/2008-July/msg00429.html
+             */
+        } caps;
+    } source;
+    char* target;
+    virDomainHostdevDefPtr next;
+};
 
 /* Flags for the 'type' field in next struct */
 enum virDomainDeviceType {
@@ -288,6 +333,7 @@ enum virDomainDeviceType {
     VIR_DOMAIN_DEVICE_NET,
     VIR_DOMAIN_DEVICE_INPUT,
     VIR_DOMAIN_DEVICE_SOUND,
+    VIR_DOMAIN_DEVICE_HOSTDEV,
 };
 
 typedef struct _virDomainDeviceDef virDomainDeviceDef;
@@ -300,6 +346,7 @@ struct _virDomainDeviceDef {
         virDomainNetDefPtr net;
         virDomainInputDefPtr input;
         virDomainSoundDefPtr sound;
+        virDomainHostdevDefPtr hostdev;
     } data;
 };
 
@@ -386,6 +433,7 @@ struct _virDomainDef {
     virDomainNetDefPtr nets;
     virDomainInputDefPtr inputs;
     virDomainSoundDefPtr sounds;
+    virDomainHostdevDefPtr hostdevs;
     virDomainChrDefPtr serials;
     virDomainChrDefPtr parallels;
     virDomainChrDefPtr console;
@@ -441,6 +489,7 @@ void virDomainFSDefFree(virDomainFSDefPtr 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);
@@ -515,6 +564,8 @@ VIR_ENUM_DECL(virDomainFS)
 VIR_ENUM_DECL(virDomainNet)
 VIR_ENUM_DECL(virDomainChr)
 VIR_ENUM_DECL(virDomainSoundModel)
+VIR_ENUM_DECL(virDomainHostdevMode)
+VIR_ENUM_DECL(virDomainHostdevSubsys)
 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 f5d12c7..73039c5 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;
@@ -1152,6 +1153,34 @@ int qemudBuildCommandLine(virConnectPtr conn,
         ADD_ARG(modstr);
     }
 
+    /* Add host passthrough hardware */
+    while (hostdev) {
+        int ret;
+        char* usbdev;
+
+        if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
+	    hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
+            if(hostdev->source.subsys.usb.vendor) {
+                    ret = asprintf(&usbdev, "host:%.4x:%.4x",
+                               hostdev->source.subsys.usb.vendor,
+                               hostdev->source.subsys.usb.product);
+
+            } else {
+                    ret = asprintf(&usbdev, "host:%.3d.%.3d",
+                               hostdev->source.subsys.usb.bus,
+                               hostdev->source.subsys.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 7fe3903..1aff53e 100644
--- a/src/qemu_driver.c
+++ b/src/qemu_driver.c
@@ -2951,12 +2951,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->source.subsys.usb.vendor) {
+        ret = asprintf(&cmd, "usb_add host:%.4x:%.4x",
+                       dev->data.hostdev->source.subsys.usb.vendor,
+                       dev->data.hostdev->source.subsys.usb.product);
+    } else {
+        ret = asprintf(&cmd, "usb_add host:%.3d.%.3d",
+                       dev->data.hostdev->source.subsys.usb.bus,
+                       dev->data.hostdev->source.subsys.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,
@@ -2975,36 +3047,21 @@ 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_HOSTDEV &&
+        dev->data.hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
+        dev->data.hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_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

[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]