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