@@ -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 {
@@ -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;
@@ -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
@@ -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
@@ -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