[RFC 1/4] KVM in-kernel PM Timer implementation (experimental code part 1)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux