PVBUS allows VMM agnostic PV drivers to discover/configure virtual resources

Signed-off-by: Gregory Haskins <[EMAIL PROTECTED]>
---

 drivers/kvm/Kconfig      |    2 
 drivers/kvm/Makefile     |    1 
 drivers/kvm/kvm.h        |    3 
 drivers/kvm/kvm_main.c   |    4 
 drivers/kvm/pvbus_host.c |  636 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/kvm/pvbus_host.h |   66 +++++
 6 files changed, 711 insertions(+), 1 deletions(-)

diff --git a/drivers/kvm/Kconfig b/drivers/kvm/Kconfig
index b81a188..b45c9c3 100644
--- a/drivers/kvm/Kconfig
+++ b/drivers/kvm/Kconfig
@@ -45,7 +45,7 @@ config KVM_PV_HOST
         boolean "Add paravirtualization backend support to KVM"
        depends on KVM
        select IOQ
-        
+
 config KVM_GUEST
        bool "KVM Guest support"
        depends on X86
diff --git a/drivers/kvm/Makefile b/drivers/kvm/Makefile
index eb32ce5..e7b52e8 100644
--- a/drivers/kvm/Makefile
+++ b/drivers/kvm/Makefile
@@ -5,6 +5,7 @@
 kvm-objs := kvm_main.o mmu.o x86_emulate.o
 ifeq ($(CONFIG_KVM_PV_HOST),y)
 kvm-objs += ioq_host.o
+kvm-objs += pvbus_host.o
 endif
 obj-$(CONFIG_KVM) += kvm.o
 kvm-intel-objs = vmx.o
diff --git a/drivers/kvm/kvm.h b/drivers/kvm/kvm.h
index aaa6d12..9f1cdfa 100644
--- a/drivers/kvm/kvm.h
+++ b/drivers/kvm/kvm.h
@@ -14,6 +14,8 @@
 #include <linux/sched.h>
 #include <linux/mm.h>
 #include <linux/preempt.h>
+#include <linux/pvbus.h>
+  
 #include <asm/signal.h>
 
 #include "ioq.h"
@@ -414,6 +416,7 @@ struct kvm {
        struct kvm_io_bus pio_bus;
 #ifdef CONFIG_KVM_PV_HOST
        struct ioq_mgr *ioqmgr;
+       struct kvm_pvbus *pvbus;
 #endif
 };
 
diff --git a/drivers/kvm/kvm_main.c b/drivers/kvm/kvm_main.c
index 03d0d67..8d1e4ce 100644
--- a/drivers/kvm/kvm_main.c
+++ b/drivers/kvm/kvm_main.c
@@ -18,6 +18,7 @@
 #include "kvm.h"
 #include "x86_emulate.h"
 #include "segment_descriptor.h"
+#include "pvbus_host.h"
 
 #include <linux/kvm.h>
 #include <linux/module.h>
@@ -302,6 +303,7 @@ static struct kvm *kvm_create_vm(void)
        list_add(&kvm->vm_list, &vm_list);
        spin_unlock(&kvm_lock);
        kvmhost_ioqmgr_init(kvm);
+       kvm_pvbus_init(kvm);
        return kvm;
 }
 
@@ -3305,6 +3307,7 @@ static __init int kvm_init(void)
        memset(__va(bad_page_address), 0, PAGE_SIZE);
 
        kvmhost_ioqmgr_module_init();
+       kvm_pvbus_module_init();
 
        return 0;
 
@@ -3320,6 +3323,7 @@ static __exit void kvm_exit(void)
        kvm_exit_debug();
        __free_page(pfn_to_page(bad_page_address >> PAGE_SHIFT));
        kvm_mmu_module_exit();
+       kvm_pvbus_module_exit();
 }
 
 module_init(kvm_init)
diff --git a/drivers/kvm/pvbus_host.c b/drivers/kvm/pvbus_host.c
new file mode 100644
index 0000000..cc506f4
--- /dev/null
+++ b/drivers/kvm/pvbus_host.c
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2007 Novell.  All Rights Reserved.
+ *
+ * Author:
+ *     Gregory Haskins <[EMAIL PROTECTED]>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/rbtree.h>
+#include <linux/spinlock.h>
+#include <linux/highmem.h>
+#include <linux/workqueue.h>
+
+#include "pvbus.h"
+#include "pvbus_host.h"
+#include "kvm.h"
+
+struct pvbus_map {
+       int         (*compare)(const void *left, const void *right);
+       const void* (*getkey)(struct rb_node *node);
+
+       struct mutex   lock;
+       struct rb_root root;
+       size_t         count;
+};
+
+struct _pv_devtype {
+       struct kvm_pv_devtype *item;
+       struct rb_node         node;
+};
+
+struct _pv_device {
+       struct kvm_pv_device  *item;
+       struct rb_node         node;
+       struct _pv_devtype    *parent;
+       int                    synced;
+};
+
+static struct pvbus_map pvbus_typemap;
+
+struct kvm_pvbus_eventq {
+       struct mutex     lock;
+       struct ioq      *ioq;
+
+};
+
+struct kvm_pvbus {
+       struct mutex            lock;
+       struct kvm             *kvm;
+       struct pvbus_map        devmap;
+       struct kvm_pvbus_eventq eventq;
+};
+
+/*
+ * ------------------
+ * generic rb map management
+ * ------------------
+ */
+
+static void pvbus_map_init(struct pvbus_map *map)
+{
+       mutex_init(&map->lock);
+       map->root = RB_ROOT;
+}
+
+static int pvbus_map_register(struct pvbus_map *map, struct rb_node *node)
+{
+       int             ret = 0;
+       struct rb_root *root;
+       struct rb_node **new, *parent = NULL;
+
+       mutex_lock(&map->lock);
+
+       root = &map->root;
+       new  = &(root->rb_node);
+
+       /* Figure out where to put new node */
+       while (*new) {
+               int result = map->compare(map->getkey(node),
+                                         map->getkey(*new));
+
+               parent = *new;
+
+               if (result < 0)
+                       new = &((*new)->rb_left);
+               else if (result > 0)
+                       new = &((*new)->rb_right);
+               else {
+                       ret = -EEXIST;
+                       break;
+               }
+       }
+
+       if (!ret) {
+               /* Add new node and rebalance tree. */
+               rb_link_node(node, parent, new);
+               rb_insert_color(node, root);
+               map->count++;
+       }
+
+       mutex_unlock(&map->lock);
+
+       return ret;
+}
+
+static struct rb_node* pvbus_map_find(struct pvbus_map *map, const void *key)
+{
+       struct rb_node *node;
+
+       mutex_lock(&map->lock);
+
+       node = map->root.rb_node;
+
+       while (node) {
+               int result = map->compare(map->getkey(node), key);
+
+               if (result < 0)
+                       node = node->rb_left;
+               else if (result > 0)
+                       node = node->rb_right;
+               else {
+                       break;
+               }
+       }
+
+       mutex_unlock(&map->lock);
+
+       return node;
+}
+
+static void pvbus_map_erase(struct pvbus_map *map, struct rb_node *node)
+{
+       mutex_lock(&map->lock);
+       rb_erase(node, &map->root);
+       map->count--;
+       mutex_unlock(&map->lock);
+}
+
+/*
+ * ------------------
+ * pv_devtype rb map
+ * ------------------
+ */
+static int pv_devtype_map_compare(const void *left, const void *right)
+{
+       return strcmp((char*)left, (char*)right);
+}
+
+static const void* pv_devtype_map_getkey(struct rb_node *node)
+{
+       struct _pv_devtype *dt;
+
+       dt = container_of(node, struct _pv_devtype, node);
+
+       return dt->item->name;
+}
+
+static void pv_devtype_map_init(struct pvbus_map *map)
+{
+       pvbus_map_init(map);
+
+       map->compare   = pv_devtype_map_compare;
+       map->getkey    = pv_devtype_map_getkey;
+}
+
+static struct _pv_devtype* devtype_map_find(struct pvbus_map *map,
+                                           const void *key)
+{
+       struct rb_node *node = pvbus_map_find(map, key);
+       if (!node)
+               return NULL;
+
+       return container_of(node, struct _pv_devtype, node);
+}
+
+/*
+ * ------------------
+ * pv_device rb map
+ * ------------------
+ */
+static int pv_device_map_compare(const void *left, const void *right)
+{
+       u64 lid = *(const u64*)left;
+       u64 rid = *(const u64*)right;
+
+       return lid - rid;
+}
+
+static const void* pv_device_map_getkey(struct rb_node *node)
+{
+       struct _pv_device *dev;
+
+       dev = container_of(node, struct _pv_device, node);
+
+       return &dev->item->id;
+}
+
+static void pv_device_map_init(struct pvbus_map *map)
+{
+       pvbus_map_init(map);
+
+       map->compare   = pv_device_map_compare;
+       map->getkey    = pv_device_map_getkey;
+}
+
+static struct _pv_device* device_map_find(struct pvbus_map *map,
+                                         const void *key)
+{
+       struct rb_node *node = pvbus_map_find(map, key);
+       if (!node)
+               return NULL;
+
+       return container_of(node, struct _pv_device, node);
+}
+
+/*
+ * ------------------
+ * event-inject code
+ * ------------------
+ */
+static void kvm_pvbus_inject_event(struct kvm_pvbus *pvbus, u32 eventid,
+                                  void *data, size_t len)
+{
+       DECLARE_WAITQUEUE(wait, current);
+       struct kvm_pvbus_eventq *eventq = &pvbus->eventq;
+       struct ioq_iterator iter;
+       struct pvbus_event *entry;
+       int ret;
+
+       add_wait_queue(&eventq->ioq->wq, &wait);
+
+       mutex_lock(&eventq->lock);
+
+       /* We want to iterate on the head of the in-use index */
+       ret = ioq_iter_init(eventq->ioq, &iter,
+                           ioq_idxtype_inuse, IOQ_ITER_AUTOUPDATE);
+       BUG_ON(ret < 0);
+
+       ret = ioq_iter_seek(&iter, ioq_seek_head, 0, 0);
+       BUG_ON(ret < 0);
+
+       set_current_state(TASK_UNINTERRUPTIBLE);
+
+       while (!iter.desc->sown)
+               schedule();
+
+       set_current_state(TASK_RUNNING);
+
+       entry = (struct pvbus_event*)gpa_to_hva(pvbus->kvm, iter.desc->ptr);
+
+       entry->eventid = eventid;
+       memcpy(&entry->data, data, len);
+
+       mb();
+       iter.desc->sown = 0;
+       mb();
+       
+       /*
+        * This will push the index AND signal the guest since AUTOUPDATE is
+        * enabled
+        */
+       ret = ioq_iter_push(&iter, 0);
+       BUG_ON(ret < 0);
+
+       /* FIXME: Unmap the entry */
+
+       mutex_unlock(&eventq->lock);
+}
+
+static void kvm_pvbus_inject_add(struct kvm_pvbus *pvbus,
+                                const char *name, u64 id)
+{
+       struct pvbus_add_event data = {
+               .id   = id,
+       };
+
+       strncpy(data.name, name, PVBUS_MAX_NAME);
+
+       kvm_pvbus_inject_event(pvbus, KVM_PVBUS_EVENT_ADD,
+                              &data, sizeof(data));
+}
+
+/*
+ * ------------------
+ * add-event code
+ * ------------------
+ */
+
+struct deferred_add {
+       struct kvm_pvbus      *pvbus;
+       struct work_struct     work;
+       size_t                 count;
+       struct pvbus_add_event data[1];
+};
+
+static void kvm_pvbus_deferred_resync(struct work_struct *work)
+{
+       struct deferred_add *event = container_of(work,
+                                                 struct deferred_add,
+                                                 work);
+       int i;
+
+
+       for (i = 0; i < event->count; i++) {
+               struct pvbus_add_event *entry = &event->data[i];
+
+               kvm_pvbus_inject_add(event->pvbus, entry->name, entry->id);
+       }
+
+       kfree(event);
+}
+
+#define for_each_rbnode(node, root) \
+        for (node = rb_first(root); node != NULL; node = rb_next(node))
+
+/*
+ * This function will build a list of all currently registered devices and
+ * send it to a work-queue to be placed on the ioq.  We do this as a two
+ * step operation because the work-queues can queue infinitely deep
+ * (assuming enough memory) whereas IOQ is only queue as deep as the
+ * guest's allocation and then we must sleep.  Since we cannot sleep during
+ * registration we have no real choice but to defer things here
+ */
+static int kvm_pvbus_resync(struct kvm_pvbus *pvbus)
+{
+       struct pvbus_map    *map = &pvbus->devmap;
+       struct deferred_add *event;
+       struct rb_node      *node;
+       size_t               len;
+       int                  i   = 0;
+       int                  ret = 0;
+
+       mutex_lock(&map->lock);
+
+       if (!map->count)
+               /* There are no items current registered so just exit */
+               goto out;
+
+       /*
+        * First allocate a structure large enough to hold our map->count
+        * number of entries that are pending
+        */
+
+       /* we subtract 1 because of item already in struct */
+       len = sizeof(struct pvbus_add_event) * (map->count - 1);
+       event = kzalloc(sizeof(*event) + len, GFP_KERNEL);
+       if (!event) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       event->pvbus = pvbus;
+       event->count = map->count;
+       INIT_WORK(&event->work, kvm_pvbus_deferred_resync);
+
+       /*
+        * Then cycle through the map and load each node discovered into
+        * the event
+        */
+       for_each_rbnode(node, &map->root) {
+               struct pvbus_add_event *entry = &event->data[i++];
+               struct _pv_device      *dev   = container_of(node,
+                                                            struct _pv_device,
+                                                            node);
+
+               strncpy(entry->name, dev->parent->item->name, PVBUS_MAX_NAME);
+               entry->id = dev->item->id;
+       }
+
+       /* Finally, fire off the work */
+       schedule_work(&event->work);
+
+ out:
+       mutex_unlock(&map->lock);
+
+       return 0;
+}
+
+
+/*
+ * ------------------
+ * hypercall implementation
+ * ------------------
+ */
+
+/*
+ * This function is invoked when the guest wants to start getting hotplug
+ * events from us to publish on the pvbus
+ */
+static int kvm_pvbus_register(struct kvm_pvbus *pvbus, ioq_id_t id)
+{
+       struct ioq_mgr *ioqmgr = pvbus->kvm->ioqmgr;
+       int ret = 0;
+
+       mutex_lock(&pvbus->lock);
+
+       /*
+        * Trying to register while someone else is already registered
+        * is just plain illegal
+        */
+       if (pvbus->eventq.ioq) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /*
+        * Open the IOQ channel back to the guest so we can deliver hotplug
+        * events as devices are registered
+        */
+       ret = ioqmgr->connect(ioqmgr, id, &pvbus->eventq.ioq, 0);
+       if (ret < 0)
+               goto out;
+
+       /*
+        * Enable interrupts on the queue
+        */
+       ioq_start(pvbus->eventq.ioq, 0);
+
+       /*
+        * Now we need to backfill the guest by sending any of our currently
+        * registered devices up as hotplug events as if they just happened
+        */
+       ret = kvm_pvbus_resync(pvbus);
+
+ out:
+       mutex_unlock(&pvbus->lock);
+
+       return ret;
+}
+
+/*
+ * This function is invoked whenever a driver calls pvbus_device->call()
+ */
+static int kvm_pvbus_call(struct kvm_pvbus *pvbus,
+                         u64 instance, u32 func, void *data, size_t len)
+{
+       struct kvm_pv_device *dev;
+       struct _pv_device    *_dev = device_map_find(&pvbus->devmap,
+                                                    &instance);
+       if (!_dev)
+               return -ENOENT;
+
+       dev = _dev->item;
+
+       return dev->call(dev, func, data, len);
+}
+
+/*
+ * Our hypercall format will always follow with the call-id in arg[0],
+ * a pointer to the arguments in arg[1], and the argument length in arg[2]
+ */
+static unsigned long kvm_pvbus_hc(struct kvm_vcpu *vcpu,
+                                 unsigned long args[])
+{
+       struct kvm_pvbus *pvbus = vcpu->kvm->pvbus;
+       void *vdata = (void*)gpa_to_hva(vcpu->kvm, args[1]);
+       int ret = -EINVAL;
+
+       /* FIXME: We need to validate vdata so that malicious guests cannot
+          cause the host to segfault */
+
+       switch (args[0])
+       {
+       case KVM_PVBUS_OP_REGISTER: {
+               struct pvbus_register_params *params;
+
+               params = (struct pvbus_register_params*)vdata;
+
+               ret = kvm_pvbus_register(pvbus, params->qid);
+       }
+       case KVM_PVBUS_OP_CALL: {
+               struct pvbus_call_params *params;
+               void *data;
+
+               params = (struct pvbus_call_params*)vdata;
+               data = gpa_to_hva(vcpu->kvm, params->data);
+
+               /*
+                * FIXME: Again, we should validate that
+                *
+                *    params->data to params->data+len
+                *
+                * is a valid region owned by the guest
+                */
+
+               ret = kvm_pvbus_call(pvbus, params->inst, params->func,
+                                    data, params->len);
+
+               /* FIXME: Do we need to unmap the data */
+       }
+       }
+
+       /* FIXME: Do we need to kunmap the vdata? */
+
+       return ret;
+}
+
+int kvm_pvbus_registertype(struct kvm_pv_devtype *devtype)
+{
+       struct _pv_devtype *_devtype = kzalloc(sizeof(*_devtype), GFP_KERNEL);
+       if (!_devtype)
+               return -ENOMEM;
+
+       _devtype->item = devtype;
+
+       return pvbus_map_register(&pvbus_typemap, &_devtype->node);
+}
+EXPORT_SYMBOL_GPL(kvm_pvbus_registertype);
+
+int kvm_pvbus_unregistertype(const char *name)
+{
+       /* FIXME: */
+       return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(kvm_pvbus_unregistertype);
+
+/*
+ * This function is invoked by an administrative operation which wants to
+ * instantiate a registered type into a device associated with a specific VM.
+ *
+ * For instance, QEMU may one day issue an ioctl that says
+ *
+ *         createinstance("ioqnet", "mac = 00:30:cc:00:20:10");
+ *
+ * This would cause the system to search for any registered types called
+ * "ioqnet".  If found, it would instantiate the device with a config string
+ * set to give it a specific MAC.  Obviously the name and config string are
+ * specific to a particular driver type.
+ */
+int kvm_pvbus_createinstance(struct kvm *kvm, const char *name,
+                            const char *cfg, u64 *id)
+{
+       struct kvm_pvbus      *pvbus = kvm->pvbus;
+       struct _pv_devtype    *_devtype;
+       struct kvm_pv_devtype *devtype;
+       struct _pv_device     *_dev = NULL;
+       struct kvm_pv_device  *dev;
+       u64                    _id;
+       int                    ret = 0;
+
+       mutex_lock(&pvbus->lock);
+
+       _devtype = devtype_map_find(&pvbus_typemap, name);
+       if (!_devtype) {
+               ret = -ENOENT;
+               goto out_err;
+       }
+
+       devtype = _devtype->item;
+
+       _dev = kzalloc(sizeof(*_dev), GFP_KERNEL);
+       if (!_dev) {
+               ret = -ENOMEM;
+               goto out_err;
+       }
+
+       /* We just use the pointer address as a unique id */
+       _id = (u64)_dev;
+
+       ret = devtype->create(kvm, devtype, _id, cfg, &dev);
+       if (ret < 0)
+               goto out_err;
+
+       _dev->item   = dev;
+       _dev->parent = _devtype;
+
+       pvbus_map_register(&pvbus->devmap, &_dev->node);
+
+       mutex_unlock(&pvbus->lock);
+
+       *id = _id;
+
+       kvm_pvbus_inject_add(pvbus, name, _id);
+
+       return 0;
+
+ out_err:
+       mutex_unlock(&pvbus->lock);
+
+       kfree(_dev);
+
+       return ret;
+}
+
+int kvm_pvbus_init(struct kvm *kvm)
+{
+       struct kvm_pvbus *pvbus = kzalloc(sizeof(*pvbus), GFP_KERNEL);
+       if (!pvbus)
+               return -ENOMEM;
+
+       mutex_init(&pvbus->lock);
+       pvbus->kvm = kvm;
+       pv_device_map_init(&pvbus->devmap);
+       mutex_init(&pvbus->eventq.lock);
+
+       kvm->pvbus = pvbus;
+
+       return 0;
+}
+
+__init int kvm_pvbus_module_init(void)
+{
+       struct kvm_hypercall hc;
+
+       pv_devtype_map_init(&pvbus_typemap);
+
+       /* Register our hypercall */
+       hc.hypercall      = kvm_pvbus_hc;
+       hc.idx            = __NR_hypercall_pvbus;
+
+       kvm_register_hypercall(THIS_MODULE, &hc);
+
+       return 0;
+}
+
+ __exit void kvm_pvbus_module_exit(void)
+{
+       /* FIXME: Unregister our hypercall */
+}
+
+
+
+
diff --git a/drivers/kvm/pvbus_host.h b/drivers/kvm/pvbus_host.h
new file mode 100644
index 0000000..16de670
--- /dev/null
+++ b/drivers/kvm/pvbus_host.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2007 Novell.  All Rights Reserved.
+ *
+ * Author:
+ *      Gregory Haskins <[EMAIL PROTECTED]>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KVM_PVBUS_HOST_H
+#define _KVM_PVBUS_HOST_H
+
+#include <linux/rbtree.h>
+
+#ifdef CONFIG_KVM_PV_HOST
+
+struct kvm;
+
+struct kvm_pvbus;
+
+struct kvm_pv_device {
+       int (*call)(struct kvm_pv_device *t, u32 func, void *data, size_t len);
+       void (*destroy)(struct kvm_pv_device *t);
+
+       u64            id;
+       u32            ver;
+
+};
+
+struct kvm_pv_devtype {
+       int (*create)(struct kvm *kvm,
+                     struct kvm_pv_devtype *t, u64 id, const char *cfg,
+                     struct kvm_pv_device **dev);
+       void (*destroy)(struct kvm_pv_devtype *t);
+
+       const char     *name;
+};
+
+int kvm_pvbus_init(struct kvm *kvm);
+int kvm_pvbus_module_init(void);
+void kvm_pvbus_module_exit(void);
+int kvm_pvbus_registertype(struct kvm_pv_devtype *devtype);
+int kvm_pvbus_unregistertype(const char *name);
+int kvm_pvbus_createinstance(struct kvm *kvm, const char *name,
+                            const char *config, u64 *id);
+
+#else /* CONFIG_KVM_PV_HOST */
+
+#define kvm_pvbus_init(kvm) {}
+#define kvm_pvbus_module_init() {}
+#define kvm_pvbus_module_exit() {}
+
+#endif /* CONFIG_KVM_PV_HOST */
+
+#endif /* _KVM_PVBUS_HOST_H */


-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >>  http://get.splunk.com/
_______________________________________________
kvm-devel mailing list
kvm-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/kvm-devel

Reply via email to