experimental code part 1 (KVM kernel) ------------------------------------- This code introduces the actual emulation of the PM Timer register plus some helper functions to create and configure the in-kernel PM Timer. The emulation utilizes the 'kvm_io_bus' infrastructure. 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 <linux/kvm_host.h> + +#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 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html