From patchwork Tue Dec 14 12:11:47 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ulrich Obergfell X-Patchwork-Id: 409661 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id oBECBpfW019251 for ; Tue, 14 Dec 2010 12:11:51 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758232Ab0LNMLs (ORCPT ); Tue, 14 Dec 2010 07:11:48 -0500 Received: from mx3-phx2.redhat.com ([209.132.183.24]:35806 "EHLO mx3-phx2.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756148Ab0LNMLr (ORCPT ); Tue, 14 Dec 2010 07:11:47 -0500 Received: from mail03.corp.redhat.com (zmail07.collab.prod.int.phx2.redhat.com [10.5.5.47]) by mx3-phx2.redhat.com (8.13.8/8.13.8) with ESMTP id oBECBlJY006341; Tue, 14 Dec 2010 07:11:47 -0500 Date: Tue, 14 Dec 2010 07:11:47 -0500 (EST) From: Ulrich Obergfell To: kvm@vger.kernel.org Cc: glommer@redhat.com, zamsden@redhat.com, avi@redhat.com, mtosatti@redhat.com Message-ID: <1590378780.680931292328707589.JavaMail.root@zmail07.collab.prod.int.phx2.redhat.com> In-Reply-To: <923827175.679451292327146652.JavaMail.root@zmail07.collab.prod.int.phx2.redhat.com> Subject: [RFC 1/4] KVM in-kernel PM Timer implementation (experimental code part 1) MIME-Version: 1.0 X-Originating-IP: [10.5.5.71] X-Mailer: Zimbra 5.0.21_GA_3150.RHEL4_64 (ZimbraWebClient - FF3.0 (Linux)/5.0.21_GA_3150.RHEL4_64) Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Tue, 14 Dec 2010 12:11:51 +0000 (UTC) diff -up ./arch/x86/include/asm/kvm_host.h.orig1 ./arch/x86/include/asm/kvm_host.h --- ./arch/x86/include/asm/kvm_host.h.orig1 2010-12-05 09:35:17.000000000 +0100 +++ ./arch/x86/include/asm/kvm_host.h 2010-12-10 12:14:29.282686691 +0100 @@ -459,6 +459,10 @@ struct kvm_arch { /* fields used by HYPER-V emulation */ u64 hv_guest_os_id; u64 hv_hypercall; + +#ifdef KVM_CAP_PMTMR + struct kvm_pmtmr *vpmtmr; +#endif }; struct kvm_vm_stat { diff -up ./arch/x86/kvm/i8254.c.orig1 ./arch/x86/kvm/i8254.c --- ./arch/x86/kvm/i8254.c.orig1 2010-12-05 09:35:17.000000000 +0100 +++ ./arch/x86/kvm/i8254.c 2010-12-10 12:09:36.877729064 +0100 @@ -51,7 +51,7 @@ #define RW_STATE_WORD1 4 /* Compute with 96 bit intermediate result: (a*b)/c */ -static u64 muldiv64(u64 a, u32 b, u32 c) +u64 muldiv64(u64 a, u32 b, u32 c) { union { u64 ll; diff -up ./arch/x86/kvm/Makefile.orig1 ./arch/x86/kvm/Makefile --- ./arch/x86/kvm/Makefile.orig1 2010-12-05 09:35:17.000000000 +0100 +++ ./arch/x86/kvm/Makefile 2010-12-10 12:07:14.379811121 +0100 @@ -12,7 +12,7 @@ kvm-$(CONFIG_IOMMU_API) += $(addprefix . kvm-$(CONFIG_KVM_ASYNC_PF) += $(addprefix ../../../virt/kvm/, async_pf.o) kvm-y += x86.o mmu.o emulate.o i8259.o irq.o lapic.o \ - i8254.o timer.o + i8254.o timer.o pmtmr.o kvm-intel-y += vmx.o kvm-amd-y += svm.o diff -up ./arch/x86/kvm/pmtmr.c.orig1 ./arch/x86/kvm/pmtmr.c --- ./arch/x86/kvm/pmtmr.c.orig1 2010-12-10 12:05:39.878691941 +0100 +++ ./arch/x86/kvm/pmtmr.c 2010-12-10 12:06:00.987738524 +0100 @@ -0,0 +1,151 @@ +/* + * in-kernel ACPI PM Timer emulation + * + * Note: 'timer carry interrupt' is not implemented + */ + +#include + +#ifdef KVM_CAP_PMTMR + +#include "pmtmr.h" + +static int emulate_acpi_reg_pmtmr(struct kvm_pmtmr *pmtmr, void *data, int len) +{ + s64 tmp; + u32 running_count; + + if (len != 4) + return -EOPNOTSUPP; + + tmp = ktime_to_ns(ktime_get()) + pmtmr->clock_offset; + running_count = (u32)muldiv64(tmp, KVM_ACPI_PMTMR_FREQ, NSEC_PER_SEC); + *(u32 *)data = running_count & KVM_ACPI_PMTMR_MASK; + +#ifdef KVM_ACPI_PMTMR_STATS + pmtmr->read_count++; +#endif + return 0; +} + +/* + * This function returns true for I/O ports in the range from 'PM base' + * to 'PM Timer' (this range contains the PM1 Status and the PM1 Enable + * registers). + */ +static inline int pmtmr_in_range(struct kvm_pmtmr *pmtmr, gpa_t ioport) +{ + return ((ioport >= pmtmr->pm_io_base) && + (ioport <= pmtmr->pm_io_base + KVM_ACPI_REG_PMTMR)); +} + +static inline struct kvm_pmtmr *dev_to_pmtmr(struct kvm_io_device *dev) +{ + return container_of(dev, struct kvm_pmtmr, dev); +} + +static int pmtmr_ioport_read(struct kvm_io_device *this, + gpa_t ioport, int len, void *data) +{ + struct kvm_pmtmr *pmtmr = dev_to_pmtmr(this); + + if (!pmtmr_in_range(pmtmr, ioport)) + return -EOPNOTSUPP; + + switch (ioport - pmtmr->pm_io_base) { + case KVM_ACPI_REG_PMTMR: + /* emulate PM Timer read if in-kernel emulation is enabled */ + if (pmtmr->state == KVM_PMTMR_STATE_ENABLED) + return(emulate_acpi_reg_pmtmr(pmtmr, data, len)); + + /* fall thru */ + default: + /* let qemu userspace handle everything else */ + return -EOPNOTSUPP; + } +} + +static int pmtmr_ioport_write(struct kvm_io_device *this, + gpa_t ioport, int len, const void *data) +{ + struct kvm_pmtmr *pmtmr = dev_to_pmtmr(this); + + if (!pmtmr_in_range(pmtmr, ioport)) + return -EOPNOTSUPP; + + switch (ioport - pmtmr->pm_io_base) { + case KVM_ACPI_REG_PMTMR: + /* ignore PM Timer write */ + return 0; + case KVM_ACPI_REG_PMEN: + if (len == 2) { + u16 val = *(u16 *)data; + /* + * Fall back to qemu userspace PM Timer emulation if + * the VM sets the 'timer carry interrupt enable' bit + * in the PM1 Enable register. + */ + if (val & KVM_ACPI_PMTMR_TMR_EN) + /* disable in-kernel PM Timer emulation */ + pmtmr->state = KVM_PMTMR_STATE_DISABLED; + } + /* fall thru */ + default: + /* let qemu userspace handle everything else */ + return -EOPNOTSUPP; + } +} + +static void pmtmr_destroy(struct kvm_io_device *this) +{ + struct kvm_pmtmr *pmtmr = dev_to_pmtmr(this); + kfree(pmtmr); +} + +static const struct kvm_io_device_ops pmtmr_dev_ops = { + .read = pmtmr_ioport_read, + .write = pmtmr_ioport_write, + .destructor = pmtmr_destroy +}; + +int kvm_create_pmtmr(struct kvm *kvm) +{ + struct kvm_pmtmr *pmtmr; + int ret; + + if (kvm->arch.vpmtmr) + return -EEXIST; + + pmtmr = kzalloc(sizeof(struct kvm_pmtmr), GFP_KERNEL); + if (!pmtmr) + return -ENOMEM; + + kvm_iodevice_init(&pmtmr->dev, &pmtmr_dev_ops); + ret = kvm_io_bus_register_dev(kvm, KVM_PIO_BUS, &pmtmr->dev); + if (ret < 0) + goto fail; + + pmtmr->state = KVM_PMTMR_STATE_CREATED; + kvm->arch.vpmtmr = pmtmr; + return 0; +fail: + kfree(pmtmr); + return ret; +} + +int kvm_configure_pmtmr(struct kvm *kvm, struct kvm_pmtmr_config *conf) +{ + struct kvm_pmtmr *pmtmr = kvm->arch.vpmtmr; + + if (!pmtmr) + return -ENXIO; + if (pmtmr->state == KVM_PMTMR_STATE_DISABLED) + return -EINVAL; + + pmtmr->pm_io_base = conf->pm_io_base; + pmtmr->clock_offset = conf->clock_offset; + pmtmr->state = KVM_PMTMR_STATE_ENABLED; + return 0; +} + +#endif diff -up ./arch/x86/kvm/pmtmr.h.orig1 ./arch/x86/kvm/pmtmr.h --- ./arch/x86/kvm/pmtmr.h.orig1 2010-12-10 12:05:43.964878651 +0100 +++ ./arch/x86/kvm/pmtmr.h 2010-12-10 12:06:00.987738524 +0100 @@ -0,0 +1,35 @@ +#ifndef __PMTMR_H +#define __PMTMR_H + +#include "iodev.h" + +#define KVM_ACPI_PMTMR_STATS + +#define KVM_ACPI_PMTMR_FREQ 3579545 +#define KVM_ACPI_PMTMR_MASK 0xffffff + +#define KVM_ACPI_PMTMR_TMR_EN 1 /* timer carry interrupt enable bit */ + /* in PM1 Enable register */ + +#define KVM_ACPI_REG_PMEN 2 /* offset of PM1 enable register */ +#define KVM_ACPI_REG_PMTMR 8 /* offset of PM Timer register */ + +struct kvm_pmtmr { + struct kvm_io_device dev; + gpa_t pm_io_base; + s64 clock_offset; +#define KVM_PMTMR_STATE_CREATED -1 +#define KVM_PMTMR_STATE_ENABLED 0 +#define KVM_PMTMR_STATE_DISABLED 1 + u64 state; +#ifdef KVM_ACPI_PMTMR_STATS + u64 read_count; +#endif +}; + +int kvm_create_pmtmr(struct kvm *kvm); +int kvm_configure_pmtmr(struct kvm *kvm, struct kvm_pmtmr_config *conf); + +extern u64 muldiv64(u64 a, u32 b, u32 c); /* defined in i8254.c */ + +#endif