Signed-off-by: Gregory Haskins <[EMAIL PROTECTED]> --- drivers/kvm/Kconfig | 16 ++ drivers/kvm/Makefile | 2 drivers/kvm/ioq.h | 39 +++++ drivers/kvm/ioq_guest.c | 196 +++++++++++++++++++++++ drivers/kvm/pvbus.h | 63 +++++++ drivers/kvm/pvbus_guest.c | 382 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/kvm.h | 4 7 files changed, 701 insertions(+), 1 deletions(-)
diff --git a/drivers/kvm/Kconfig b/drivers/kvm/Kconfig index 445c6e4..d17ce96 100644 --- a/drivers/kvm/Kconfig +++ b/drivers/kvm/Kconfig @@ -41,4 +41,20 @@ config KVM_AMD Provides support for KVM on AMD processors equipped with the AMD-V (SVM) extensions. +config KVM_GUEST + bool "KVM Guest support" + depends on X86 + default n + +config KVM_PVBUS_GUEST + tristate "Paravirtualized Bus (PVBUS) support" + depends on KVM_GUEST + select IOQ + select PVBUS + ---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 this kernel as a KVM guest. + endif # VIRTUALIZATION diff --git a/drivers/kvm/Makefile b/drivers/kvm/Makefile index c0a789f..cd621fc 100644 --- a/drivers/kvm/Makefile +++ b/drivers/kvm/Makefile @@ -8,3 +8,5 @@ kvm-intel-objs = vmx.o obj-$(CONFIG_KVM_INTEL) += kvm-intel.o kvm-amd-objs = svm.o obj-$(CONFIG_KVM_AMD) += kvm-amd.o +kvm-pvbus-objs := ioq_guest.o pvbus_guest.o +obj-$(CONFIG_KVM_PVBUS_GUEST) += kvm-pvbus.o \ No newline at end of file diff --git a/drivers/kvm/ioq.h b/drivers/kvm/ioq.h new file mode 100644 index 0000000..7e955f1 --- /dev/null +++ b/drivers/kvm/ioq.h @@ -0,0 +1,39 @@ +/* + * Copyright 2007 Novell. All Rights Reserved. + * + * See include/linux/ioq.h for documentation + * + * 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_IOQ_H_ +#define _KVM_IOQ_H_ + +#include <linux/ioq.h> + +#define IOQHC_REGISTER 1 +#define IOQHC_UNREGISTER 2 +#define IOQHC_SIGNAL 3 + +struct ioq_register { + ioq_id_t id; + u32 irq; + u64 ring; +}; + + +#endif /* _KVM_IOQ_H_ */ diff --git a/drivers/kvm/ioq_guest.c b/drivers/kvm/ioq_guest.c new file mode 100644 index 0000000..5f16390 --- /dev/null +++ b/drivers/kvm/ioq_guest.c @@ -0,0 +1,196 @@ +/* + * Copyright 2007 Novell. All Rights Reserved. + * + * See include/linux/ioq.h for documentation + * + * 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/interrupt.h> +#include <linux/ioq.h> +#include <asm/hypercall.h> + +#include "ioq.h" +#include "kvm.h" + +struct kvmguest_ioq { + struct ioq ioq; + int irq; +}; + +struct kvmguest_ioq* to_ioq(struct ioq *ioq) +{ + return container_of(ioq, struct kvmguest_ioq, ioq); +} + +static int ioq_hypercall(unsigned long nr, void *data) +{ + return hypercall(2, __NR_hypercall_ioq, nr, __pa(data)); +} + +/* + * ------------------ + * interrupt handler + * ------------------ + */ +irqreturn_t kvmguest_ioq_intr(int irq, void *dev) +{ + struct kvmguest_ioq *_ioq = to_ioq(dev); + + ioq_wakeup(&_ioq->ioq); + + return IRQ_HANDLED; +} + +/* + * ------------------ + * ioq implementation + * ------------------ + */ + +static int kvmguest_ioq_signal(struct ioq *ioq) +{ + return ioq_hypercall(IOQHC_SIGNAL, &ioq->id); +} + +static void kvmguest_ioq_destroy(struct ioq *ioq) +{ + struct kvmguest_ioq *_ioq = to_ioq(ioq); + int ret; + + ret = ioq_hypercall(IOQHC_UNREGISTER, &ioq->id); + BUG_ON (ret < 0); + + free_irq(_ioq->irq, NULL); + destroy_irq(_ioq->irq); + + kfree(_ioq->ioq.ring); + kfree(_ioq->ioq.head_desc); + kfree(_ioq); +} + +/* + * ------------------ + * ioqmgr implementation + * ------------------ + */ +static int kvmguest_ioq_register(struct kvmguest_ioq *ioq, ioq_id_t id, + int irq, void *ring) +{ + struct ioq_register data = { + .id = id, + .irq = irq, + .ring = (u64)__pa(ring), + }; + + return ioq_hypercall(IOQHC_REGISTER, &data); +} + +static int kvmguest_ioq_create(struct ioq_mgr *t, struct ioq **ioq, + size_t ringsize, int flags) +{ + struct kvmguest_ioq *_ioq = NULL; + struct ioq_ring_head *head_desc = NULL; + void *ring = NULL; + size_t ringlen = sizeof(struct ioq_ring_desc) * ringsize; + int ret = -ENOMEM; + + _ioq = kzalloc(sizeof(*_ioq), GFP_KERNEL); + if (!_ioq) + goto error; + + head_desc = kzalloc(sizeof(*head_desc), GFP_KERNEL | GFP_DMA); + if (!head_desc) + goto error; + + ring = kzalloc(ringlen, GFP_KERNEL | GFP_DMA); + if (!ring) + goto error; + + head_desc->magic = IOQ_RING_MAGIC; + head_desc->ver = IOQ_RING_VER; + head_desc->id = (ioq_id_t)_ioq; + head_desc->count = ringsize; + head_desc->ptr = (u64)__pa(ring); + + /* Dynamically assign a free IRQ to this resource */ + _ioq->irq = create_irq(); + + ioq_init(&_ioq->ioq); + + _ioq->ioq.signal = kvmguest_ioq_signal; + _ioq->ioq.destroy = kvmguest_ioq_destroy; + + _ioq->ioq.id = head_desc->id; + _ioq->ioq.locale = ioq_locality_north; + _ioq->ioq.mgr = t; + _ioq->ioq.head_desc = head_desc; + _ioq->ioq.ring = ring; + + ret = request_irq(_ioq->irq, kvmguest_ioq_intr, 0, "KVM-IOQ", _ioq); + if (ret < 0) + goto error; + + ret = kvmguest_ioq_register(_ioq, _ioq->ioq.id, _ioq->irq, ring); + if (ret < 0) + goto error; + + *ioq = &_ioq->ioq; + + return 0; + + error: + if (_ioq) + kfree(_ioq); + if (head_desc) + kfree(head_desc); + if (ring) + kfree(ring); + + return ret; +} + +static int kvmguest_ioq_connect(struct ioq_mgr *t, ioq_id_t id, + struct ioq **ioq, int flags) +{ + /* You cannot connect to queues on the guest */ + return -EINVAL; + +} + +int kvmguest_ioqmgr_alloc(struct ioq_mgr **mgr) +{ + struct ioq_mgr *_mgr = kzalloc(sizeof(*_mgr), GFP_KERNEL); + if (!_mgr) + return -ENOMEM; + + _mgr->create = kvmguest_ioq_create; + _mgr->connect = kvmguest_ioq_connect; + + *mgr = _mgr; + + return 0; +} + +void kvmguest_ioqmgr_free(struct ioq_mgr *mgr) +{ + kfree(mgr); +} + + + + diff --git a/drivers/kvm/pvbus.h b/drivers/kvm/pvbus.h new file mode 100644 index 0000000..3241ef0 --- /dev/null +++ b/drivers/kvm/pvbus.h @@ -0,0 +1,63 @@ +/* + * 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_H +#define _KVM_PVBUS_H + +#include <linux/ioq.h> + +#define KVM_PVBUS_OP_REGISTER 1 +#define KVM_PVBUS_OP_UNREGISTER 2 +#define KVM_PVBUS_OP_CALL 3 + +struct pvbus_register_params { + ioq_id_t qid; +}; + +struct pvbus_call_params { + u64 inst; + u32 func; + u64 data; + u64 len; +}; + +#define KVM_PVBUS_EVENT_ADD 1 +#define KVM_PVBUS_EVENT_DROP 2 + +#define PVBUS_MAX_NAME 128 + +struct pvbus_add_event { + char name[PVBUS_MAX_NAME]; + u64 id; +}; + +struct pvbus_drop_event { + u64 id; +}; + +struct pvbus_event { + u32 eventid; + union { + struct pvbus_add_event add; + struct pvbus_drop_event drop; + }data; +}; + +#endif /* _KVM_PVBUS_H */ diff --git a/drivers/kvm/pvbus_guest.c b/drivers/kvm/pvbus_guest.c new file mode 100644 index 0000000..56c3b50 --- /dev/null +++ b/drivers/kvm/pvbus_guest.c @@ -0,0 +1,382 @@ +/* + * 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/pvbus.h> +#include <linux/kvm_para.h> +#include <linux/kvm.h> +#include <linux/mm.h> +#include <linux/ioq.h> +#include <linux/interrupt.h> + +#include <asm/hypercall.h> + +#include "pvbus.h" + +MODULE_AUTHOR ("Gregory Haskins"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1"); + +int kvmguest_ioqmgr_alloc(struct ioq_mgr **mgr); +void kvmguest_ioqmgr_free(struct ioq_mgr *mgr); + +static int kvm_pvbus_hypercall(unsigned long nr, void *data, unsigned long len) +{ + return hypercall(3, __NR_hypercall_pvbus, nr, __pa(data), len); +} + +/* + * This is the vm-syscall address - to be patched by the host to + * VMCALL (Intel) or VMMCALL (AMD), depending on the CPU model: + */ +asm ( + " .globl hypercall_addr \n" + " .align 4 \n" + " hypercall_addr: \n" + " movl $-38, %eax \n" + " ret \n" +); + +extern unsigned char hypercall_addr[6]; + +#ifndef CONFIG_X86_64 +static DEFINE_PER_CPU(struct kvm_vcpu_para_state, para_state); +#endif + +static int __init kvm_pvbus_probe(void) +{ + struct page *hypercall_addr_page; + struct kvm_vcpu_para_state *para_state; + +#ifdef CONFIG_X86_64 + struct page *pstate_page; + if ((pstate_page = alloc_page(GFP_KERNEL)) == NULL) + return -ENOMEM; + para_state = (struct kvm_vcpu_para_state*)page_address(pstate_page); +#else + para_state = &per_cpu(para_state, cpu); +#endif + /* + * Try to write to a magic MSR (which is invalid on any real CPU), + * and thus signal to KVM that we wish to entering para-virtualized + * mode: + */ + para_state->guest_version = KVM_PARA_API_VERSION; + para_state->host_version = -1; + para_state->size = sizeof(*para_state); + para_state->ret = -1; + + hypercall_addr_page = vmalloc_to_page(hypercall_addr); + para_state->hypercall_gpa = page_to_pfn(hypercall_addr_page) + << PAGE_SHIFT | offset_in_page(hypercall_addr); + printk(KERN_DEBUG "kvm guest: hypercall gpa is 0x%lx\n", + (long)para_state->hypercall_gpa); + + if (wrmsr_safe(MSR_KVM_API_MAGIC, __pa(para_state), 0)) { + printk(KERN_INFO "KVM guest: WRMSR probe failed.\n"); + return -1; + } + + printk(KERN_DEBUG "kvm guest: host returned %d\n", + para_state->ret); + printk(KERN_DEBUG "kvm guest: host version: %d\n", + para_state->host_version); + printk(KERN_DEBUG "kvm guest: syscall entry: %02x %02x %02x %02x\n", + hypercall_addr[0], hypercall_addr[1], + hypercall_addr[2], hypercall_addr[3]); + + if (para_state->ret) { + printk(KERN_ERR "kvm guest: host refused registration.\n"); + return -1; + } + + return 0; + +} + +struct kvm_pvbus { + int connected; + struct ioq_mgr *ioqmgr; + struct ioq *ioq; + struct ioq_notifier ioqn; + struct tasklet_struct task; +}; + +static struct kvm_pvbus kvm_pvbus; + +struct kvm_pvbus_device { + struct pvbus_device pvbdev; + char name[PVBUS_MAX_NAME]; +}; + +static int kvm_pvbus_createqueue(struct pvbus_device *dev, struct ioq **ioq, + size_t ringsize, int flags) +{ + struct ioq_mgr *ioqmgr = kvm_pvbus.ioqmgr; + + return ioqmgr->create(ioqmgr, ioq, ringsize, flags); +} + +static int kvm_pvbus_call(struct pvbus_device *dev, u32 func, void *data, + size_t len, int flags) +{ + struct pvbus_call_params params = { + .inst = dev->id, + .func = func, + .data = (u64)__pa(data), + .len = len, + }; + + return kvm_pvbus_hypercall(KVM_PVBUS_OP_CALL, ¶ms, sizeof(params)); +} + +static void kvm_pvbus_add_event(struct pvbus_add_event *event) +{ + int ret; + struct kvm_pvbus_device *new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + printk("KVM_PVBUS: Out of memory on add_event\n"); + return; + } + + memcpy(new->name, event->name, PVBUS_MAX_NAME); + new->pvbdev.name = new->name; + new->pvbdev.id = event->id; + new->pvbdev.createqueue = kvm_pvbus_createqueue; + new->pvbdev.call = kvm_pvbus_call; + + sprintf(new->pvbdev.dev.bus_id, "%lld", event->id); + + ret = pvbus_device_register(&new->pvbdev); + BUG_ON(ret < 0); +} + +static void kvm_pvbus_drop_event(struct pvbus_drop_event *event) +{ +#if 0 /* FIXME */ + int ret = pvbus_device_unregister(event->id); + BUG_ON(ret < 0); +#endif +} + +/* INTR-Layer2: Invoked whenever layer 1 schedules our tasklet */ +static void kvm_pvbus_intr_l2(unsigned long _data) +{ + struct ioq_iterator iter; + int ret; + + /* We want to iterate on the tail of the in-use index */ + ret = ioq_iter_init(kvm_pvbus.ioq, &iter, ioq_idxtype_inuse, 0); + BUG_ON(ret < 0); + + ret = ioq_iter_seek(&iter, ioq_seek_tail, 0, 0); + BUG_ON(ret < 0); + + /* + * The EOM is indicated by finding a packet that is still owned by + * the south side. + * + * FIXME: This in theory could run indefinitely if the host keeps + * feeding us events since there is nothing like a NAPI budget. We + * might need to address that + */ + while (!iter.desc->sown) { + struct ioq_ring_desc *desc = iter.desc; + struct pvbus_event *event = (struct pvbus_event*)desc->cookie; + + switch (event->eventid) { + case KVM_PVBUS_EVENT_ADD: + kvm_pvbus_add_event(&event->data.add); + break; + case KVM_PVBUS_EVENT_DROP: + kvm_pvbus_drop_event(&event->data.drop); + break; + default: + printk(KERN_WARNING "KVM_PVBUS: Unexpected event %d\n", + event->eventid); + break; + }; + + memset(event, 0, sizeof(*event)); + + mb(); + desc->sown = 1; /* give ownership back to the south */ + mb(); + + /* Advance the in-use tail */ + ret = ioq_iter_pop(&iter, 0); + BUG_ON(ret < 0); + } + + /* And let the south side know that we changed the rx-queue */ + ioq_signal(kvm_pvbus.ioq, 0); +} + +/* INTR-Layer1: Invoked whenever the host issues an ioq_signal() */ +static void kvm_pvbus_intr_l1(struct ioq_notifier *ioqn) +{ + tasklet_schedule(&kvm_pvbus.task); +} + +static int __init kvm_pvbus_register(void) +{ + struct pvbus_register_params params = { + .qid = kvm_pvbus.ioq->id, + }; + + return kvm_pvbus_hypercall(KVM_PVBUS_OP_REGISTER, + ¶ms, sizeof(params)); +} + +static int __init kvm_pvbus_setup_ring(void) +{ + struct ioq *ioq = kvm_pvbus.ioq; + struct ioq_iterator iter; + int ret; + + /* + * We want to iterate on the "valid" index. By default the iterator + * will not "autoupdate" which means it will not hypercall the host + * with our changes. This is good, because we are really just + * initializing stuff here anyway. Note that you can always manually + * signal the host with ioq_signal() if the autoupdate feature is not + * used. + */ + ret = ioq_iter_init(ioq, &iter, ioq_idxtype_valid, 0); + BUG_ON(ret < 0); + + /* + * Seek to the head of the valid index (which should be our first + * item since the queue is brand-new) + */ + ret = ioq_iter_seek(&iter, ioq_seek_head, 0, 0); + BUG_ON(ret < 0); + + /* + * Now populate each descriptor with an empty pvbus_event and mark it + * valid + */ + while (!iter.desc->valid) { + struct pvbus_event *event; + size_t len = sizeof(*event); + struct ioq_ring_desc *desc = iter.desc; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + desc->cookie = (u64)event; + desc->ptr = (u64)__pa(event); + desc->len = len; /* total length */ + desc->alen = 0; /* actual length - filled in by host */ + + /* + * We don't need any barriers here because the ring is not used + * yet + */ + desc->valid = 1; + desc->sown = 1; /* give ownership to the south */ + + /* + * This push operation will simultaneously advance the + * valid-head index and increment our position in the queue + * by one. + */ + ret = ioq_iter_push(&iter, 0); + BUG_ON(ret < 0); + } + + return 0; +} + +int __init kvm_pvbus_init(void) +{ + struct ioq_mgr *ioqmgr = NULL; + int ret; + + memset(&kvm_pvbus, 0, sizeof(kvm_pvbus)); + + ret = kvm_pvbus_probe(); + if (ret < 0) + return ret; + + kvm_pvbus.connected = 1; + + /* Allocate an IOQ-manager to use for all operations */ + ret = kvmguest_ioqmgr_alloc(&ioqmgr); + if (ret < 0) { + printk(KERN_ERR "KVM_PVBUS: Could not create ioqmgr\n"); + return ret; + } + + kvm_pvbus.ioqmgr = ioqmgr; + + /* Now allocate an IOQ to use for hotplug notification */ + ret = ioqmgr->create(ioqmgr, &kvm_pvbus.ioq, 32, 0); + if (ret < 0) { + printk(KERN_ERR "KVM_PVBUS: Cound not create hotplug ioq\n"); + goto out_fail; + } + + ret = kvm_pvbus_setup_ring(); + if (ret < 0) { + printk(KERN_ERR "KVM_PVBUS: Cound not setup ring\n"); + goto out_fail; + } + + /* Setup our interrupt callback */ + kvm_pvbus.ioqn.signal = kvm_pvbus_intr_l1; + kvm_pvbus.ioq->notifier = &kvm_pvbus.ioqn; + tasklet_init(&kvm_pvbus.task, kvm_pvbus_intr_l2, 0); + + /* + * Finally register our queue on the host to start receiving hotplug + * updates + */ + ret = kvm_pvbus_register(); + if (ret < 0) { + printk(KERN_ERR "KVM_PVBUS: Could not register with host\n"); + goto out_fail; + } + + return 0; + + out_fail: + kvmguest_ioqmgr_free(ioqmgr); + + return ret; + +} + +static void __exit kvm_pvbus_exit(void) +{ + if (kvm_pvbus.connected) + kvm_pvbus_hypercall(KVM_PVBUS_OP_UNREGISTER, NULL, 0); + + if (kvm_pvbus.ioq) + kvm_pvbus.ioq->destroy(kvm_pvbus.ioq); + + kvmguest_ioqmgr_free(kvm_pvbus.ioqmgr); +} + +module_init(kvm_pvbus_init); +module_exit(kvm_pvbus_exit); + + diff --git a/include/linux/kvm.h b/include/linux/kvm.h index 7e9b862..04f65c9 100644 --- a/include/linux/kvm.h +++ b/include/linux/kvm.h @@ -314,8 +314,10 @@ struct kvm_signal_mask { * No registers are clobbered by the hypercall, except that the * return value is in RAX. */ -#define KVM_NR_HYPERCALLS 1 +#define KVM_NR_HYPERCALLS 3 #define __NR_hypercall_test 0 +#define __NR_hypercall_ioq 1 +#define __NR_hypercall_pvbus 2 #endif ------------------------------------------------------------------------- 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