kvm: add support for reading tsc frequency from kvmclock

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Message-id: 20200310102248.28412-4-kraxel@redhat.com
diff --git a/src/fw/paravirt.c b/src/fw/paravirt.c
index 1e3d601..119280c 100644
--- a/src/fw/paravirt.c
+++ b/src/fw/paravirt.c
@@ -75,6 +75,48 @@
     }
 }
 
+#define KVM_FEATURE_CLOCKSOURCE           0
+#define KVM_FEATURE_CLOCKSOURCE2          3
+
+#define MSR_KVM_SYSTEM_TIME            0x12
+#define MSR_KVM_SYSTEM_TIME_NEW  0x4b564d01
+
+#define PVCLOCK_TSC_STABLE_BIT     (1 << 0)
+
+struct pvclock_vcpu_time_info *kvmclock;
+
+static void kvmclock_init(void)
+{
+    unsigned int eax, ebx, ecx, edx, msr;
+
+    if (!runningOnKVM())
+        return;
+
+    cpuid(KVM_CPUID_SIGNATURE + 0x01, &eax, &ebx, &ecx, &edx);
+    if (eax & (1 <<  KVM_FEATURE_CLOCKSOURCE2))
+        msr = MSR_KVM_SYSTEM_TIME_NEW;
+    else if (eax & (1 <<  KVM_FEATURE_CLOCKSOURCE))
+        msr = MSR_KVM_SYSTEM_TIME;
+    else
+        return;
+
+    kvmclock = memalign_low(sizeof(*kvmclock), 32);
+    memset(kvmclock, 0, sizeof(*kvmclock));
+    u32 value = (u32)(kvmclock);
+    dprintf(1, "kvmclock: at 0x%x (msr 0x%x)\n", value, msr);
+    wrmsr(msr, value | 0x01);
+
+    if (!(kvmclock->flags & PVCLOCK_TSC_STABLE_BIT))
+        return;
+    u32 MHz = (1000 << 16) / (kvmclock->tsc_to_system_mul >> 16);
+    if (kvmclock->tsc_shift < 0)
+        MHz <<= -kvmclock->tsc_shift;
+    else
+        MHz >>= kvmclock->tsc_shift;
+    dprintf(1, "kvmclock: stable tsc, %d MHz\n", MHz);
+    tsctimer_setfreq(MHz * 1000, "kvmclock");
+}
+
 static void qemu_detect(void)
 {
     if (!CONFIG_QEMU_HARDWARE)
@@ -163,6 +205,8 @@
         return;
     }
 
+    kvmclock_init();
+
     // Initialize pci
     pci_setup();
     smm_device_setup();
diff --git a/src/fw/paravirt.h b/src/fw/paravirt.h
index f7e1d4c..4e2e993 100644
--- a/src/fw/paravirt.h
+++ b/src/fw/paravirt.h
@@ -5,6 +5,18 @@
 #include "biosvar.h" // GET_GLOBAL
 #include "romfile.h" // struct romfile_s
 
+// kvmclock
+struct pvclock_vcpu_time_info {
+	u32   version;
+	u32   pad0;
+	u64   tsc_timestamp;
+	u64   system_time;
+	u32   tsc_to_system_mul;
+	s8    tsc_shift;
+	u8    flags;
+	u8    pad[2];
+} __attribute__((__packed__)); /* 32 bytes */
+
 // Types of paravirtualized platforms.
 #define PF_QEMU     (1<<0)
 #define PF_XEN      (1<<1)