PVBUS allows VMM agnostic PV drivers to discover/configure virtual resources
Signed-off-by: Gregory Haskins <[EMAIL PROTECTED]> --- drivers/kvm/Kconfig | 10 + drivers/kvm/Makefile | 3 drivers/kvm/kvm.h | 4 drivers/kvm/kvm_main.c | 4 drivers/kvm/pvbus_host.c | 459 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/kvm/pvbus_host.h | 66 +++++++ 6 files changed, 546 insertions(+), 0 deletions(-) diff --git a/drivers/kvm/Kconfig b/drivers/kvm/Kconfig index 0f81af1..cb674bb 100644 --- a/drivers/kvm/Kconfig +++ b/drivers/kvm/Kconfig @@ -52,6 +52,16 @@ config KVM_IOQ_HOST depends on KVM select IOQ +config KVM_PVBUS_HOST + boolean "Paravirtualized Bus (PVBUS) host support" + depends on KVM + select KVM_IOQ_HOST + ---help--- + PVBUS is an infrastructure for generic PV drivers to take advantage + of an underlying hypervisor without having to understand the details + of the hypervisor itself. You only need this option if you plan to + run PVBUS based PV guests in KVM. + config KVM_NET_HOST tristate "Para virtual network host device" depends on KVM diff --git a/drivers/kvm/Makefile b/drivers/kvm/Makefile index 2095061..8926fa9 100644 --- a/drivers/kvm/Makefile +++ b/drivers/kvm/Makefile @@ -7,6 +7,9 @@ kvm-objs := kvm_main.o mmu.o x86_emulate.o ifeq ($(CONFIG_KVM_IOQ_HOST),y) kvm-objs += ioq_host.o endif +ifeq ($(CONFIG_KVM_PVBUS_HOST),y) +kvm-objs += pvbus_host.o +endif obj-$(CONFIG_KVM) += kvm.o kvm-intel-objs = vmx.o obj-$(CONFIG_KVM_INTEL) += kvm-intel.o diff --git a/drivers/kvm/kvm.h b/drivers/kvm/kvm.h index c38c84f..8dc9ac3 100755 --- a/drivers/kvm/kvm.h +++ b/drivers/kvm/kvm.h @@ -14,6 +14,7 @@ #include <linux/sched.h> #include <linux/mm.h> #include <linux/netdevice.h> +#include <linux/pvbus.h> #include "vmx.h" #include "ioq.h" @@ -393,6 +394,9 @@ struct kvm { #ifdef CONFIG_KVM_IOQ_HOST struct ioq_mgr *ioqmgr; #endif +#ifdef CONFIG_KVM_PVBUS_HOST + struct kvm_pvbus *pvbus; +#endif }; diff --git a/drivers/kvm/kvm_main.c b/drivers/kvm/kvm_main.c index fbffd2f..d35ce8d 100644 --- a/drivers/kvm/kvm_main.c +++ b/drivers/kvm/kvm_main.c @@ -44,6 +44,7 @@ #include "x86_emulate.h" #include "segment_descriptor.h" +#include "pvbus_host.h" MODULE_AUTHOR("Qumranet"); MODULE_LICENSE("GPL"); @@ -350,6 +351,7 @@ static struct kvm *kvm_create_vm(void) spin_unlock(&kvm_lock); } kvmhost_ioqmgr_init(kvm); + kvm_pvbus_init(kvm); return kvm; } @@ -3616,6 +3618,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; @@ -3637,6 +3640,7 @@ static __exit void kvm_exit(void) mntput(kvmfs_mnt); unregister_filesystem(&kvm_fs_type); 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..574ca4e --- /dev/null +++ b/drivers/kvm/pvbus_host.c @@ -0,0 +1,459 @@ +/* + * 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 "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); + + spinlock_t lock; + struct rb_root root; +}; + +struct _pv_devtype { + struct kvm_pv_devtype *item; + struct rb_node node; + struct list_head devlist; +}; + +struct _pv_device { + struct kvm_pv_device *item; + struct rb_node node; + struct list_head list; + struct _pv_devtype *parent; +}; + +static struct pvbus_map pvbus_typemap; /* stores globally available types */ + +struct kvm_pvbus { + spinlock_t lock; + struct pvbus_map typemap; /* stores locally instantiated types */ + struct pvbus_map devmap; +}; + +/* + * ------------------ + * generic rb map management + * ------------------ + */ + +static void pvbus_map_init(struct pvbus_map *map) +{ + spin_lock_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; + + spin_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); + } + + spin_unlock(&map->lock); + + return ret; +} + +static struct rb_node* pvbus_map_find(struct pvbus_map *map, const void *key) +{ + struct rb_node *node; + + spin_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; + } + } + + spin_unlock(&map->lock); + + return node; +} + +static void pvbus_map_erase(struct pvbus_map *map, struct rb_node *node) +{ + spin_lock(&map->lock); + rb_erase(node, &map->root); + spin_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); +} + +/* + * ------------------ + * hypercall implementation + * ------------------ + */ + +static int kvm_pvbus_enumerate(struct kvm_pvbus *pvbus, + const char* name, + struct pvbus_dev inst[], + u32 *cnt) +{ + struct _pv_devtype *_devtype; + struct list_head *pos; + u32 avail = *cnt; + + *cnt = 0; + + _devtype = devtype_map_find(&pvbus->typemap, name); + if (!_devtype) + return -ENOENT; + + list_for_each(pos, &_devtype->devlist) { + + /* + * If avail drops to 0 or below, it means we have more + * instances than the client has space for. So we simply stop + * recording new instances, but allow the loop to continue to + * get an accurate count. + */ + if (avail > 0) { + struct _pv_device *_dev; + struct kvm_pv_device *dev; + + _dev = container_of(pos, struct _pv_device, list); + dev = _dev->item; + + inst[*cnt].instance = dev->id; + inst[*cnt].version = dev->ver; + } + + (*cnt)++; + avail--; + + } + + /* + * We fail the call if we ran out of space to give the caller the + * opportunity to call again with a larger array + */ + if (avail < 0) + return -ENOSPC; + + return 0; +} + +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_ENUM: { + struct enumerate_params *params; + struct pvbus_dev *inst; + + params = (struct enumerate_params*)vdata; + + inst = (struct pvbus_dev*)¶ms->data[params->inst_offset]; + + ret = kvm_pvbus_enumerate(pvbus, + ¶ms->data[params->name_offset], + inst, + ¶ms->count); + } + case KVM_PVBUS_OP_CALL: { + struct call_params *params; + void *data; + + params = (struct 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; + INIT_LIST_HEAD(&_devtype->devlist); + + return pvbus_map_register(&pvbus_typemap, &_devtype->node); +} +EXPORT_SYMBOL_GPL(kvm_pvbus_registertype); + +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; + struct kvm_pv_device *dev; + u64 _id; + int ret; + + _devtype = devtype_map_find(&pvbus->typemap, name); + if (!_devtype) { + struct _pv_devtype *gdevtype; + /* + * If we get here it could be because this is the first + * time this devtype has been accessed by this particular + * vm. So we need to check the global typemap also + */ + gdevtype = devtype_map_find(&pvbus_typemap, name); + if (!gdevtype) + /* I guess this is plain old fashioned not present */ + return -ENOENT; + + /* Else, we need to promote this devtype to the current VM */ + _devtype = kzalloc(sizeof(*_devtype), GFP_KERNEL); + _devtype->item = gdevtype->item; + INIT_LIST_HEAD(&_devtype->devlist); + + /* Note that we register it with the pvbus context */ + ret = pvbus_map_register(&pvbus->typemap, &_devtype->node); + if (ret < 0) + return ret; + } + + devtype = _devtype->item; + + _dev = kzalloc(sizeof(*_dev), GFP_KERNEL); + if (!_dev) + return -ENOMEM; + + /* 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; + INIT_LIST_HEAD(&_dev->list); + _dev->parent = _devtype; + + pvbus_map_register(&pvbus->devmap, &_dev->node); + list_add_tail(&_devtype->devlist, &_dev->list); + + *id = _id; + + return 0; + + out_err: + kfree(_dev); + + return ret; +} + +int kvm_pvbus_init(struct kvm *kvm) +{ + struct kvm_pvbus *pvbus = kzalloc(sizeof(*pvbus), GFP_KERNEL); + if (!pvbus) + return -ENOMEM; + + spin_lock_init(&pvbus->lock); + pv_devtype_map_init(&pvbus->typemap); + pv_device_map_init(&pvbus->devmap); + + 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..a3cc7a0 --- /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_PVBUS_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_PVBUS_HOST */ + +#define kvm_pvbus_init(kvm) {} +#define kvm_pvbus_module_init() {} +#define kvm_pvbus_module_exit() {} + +#endif /* CONFIG_KVM_PVBUS_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