x86: Hypervisor detection and get tsc_freq from hypervisor
authorAlok Kataria <akataria@vmware.com>
Mon, 27 Oct 2008 17:41:46 +0000 (10:41 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Sun, 2 Nov 2008 01:57:08 +0000 (18:57 -0700)
Impact: Changes timebase calibration on Vmware.

v3->v2 : Abstract the hypervisor detection and feature (tsc_freq) request
 behind a hypervisor.c file
v2->v1 : Add a x86_hyper_vendor field to the cpuinfo_x86 structure.
 This avoids multiple calls to the hypervisor detection function.

This patch adds function to detect if we are running under VMware.
The current way to check if we are on VMware is following,
#  check if "hypervisor present bit" is set, if so read the 0x40000000
   cpuid leaf and check for "VMwareVMware" signature.
#  if the above fails, check the DMI vendors name for "VMware" string
   if we find one we query the VMware hypervisor port to check if we are
   under VMware.

The DMI + "VMware hypervisor port check" is needed for older VMware products,
which don't implement the hypervisor signature cpuid leaf.
Also note that since we are checking for the DMI signature the hypervisor
port should never be accessed on native hardware.

This patch also adds a hypervisor_get_tsc_freq function, instead of
calibrating the frequency which can be error prone in virtualized
environment, we ask the hypervisor for it. We get the frequency from
the hypervisor by accessing the hypervisor port if we are running on VMware.
Other hypervisors too can add code to the generic routine to get frequency on
their platform.

Signed-off-by: Alok N Kataria <akataria@vmware.com>
Signed-off-by: Dan Hecht <dhecht@vmware.com>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
arch/x86/include/asm/hypervisor.h [new file with mode: 0644]
arch/x86/include/asm/processor.h
arch/x86/include/asm/vmware.h [new file with mode: 0644]
arch/x86/kernel/cpu/Makefile
arch/x86/kernel/cpu/common.c
arch/x86/kernel/cpu/hypervisor.c [new file with mode: 0644]
arch/x86/kernel/cpu/vmware.c [new file with mode: 0644]
arch/x86/kernel/setup.c
arch/x86/kernel/tsc.c

diff --git a/arch/x86/include/asm/hypervisor.h b/arch/x86/include/asm/hypervisor.h
new file mode 100644 (file)
index 0000000..369f5c5
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2008, VMware, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef ASM_X86__HYPERVISOR_H
+#define ASM_X86__HYPERVISOR_H
+
+extern unsigned long get_hypervisor_tsc_freq(void);
+extern void init_hypervisor(struct cpuinfo_x86 *c);
+
+#endif
index 5ca01e3832699b0378c36d79a4d17361a3d27b88..a570eafa4755e6571b8e7cacc3c0746019efbf5e 100644 (file)
@@ -110,6 +110,7 @@ struct cpuinfo_x86 {
        /* Index into per_cpu list: */
        u16                     cpu_index;
 #endif
+       unsigned int            x86_hyper_vendor;
 } __attribute__((__aligned__(SMP_CACHE_BYTES)));
 
 #define X86_VENDOR_INTEL       0
@@ -123,6 +124,9 @@ struct cpuinfo_x86 {
 
 #define X86_VENDOR_UNKNOWN     0xff
 
+#define X86_HYPER_VENDOR_NONE  0
+#define X86_HYPER_VENDOR_VMWARE 1
+
 /*
  * capabilities of CPUs
  */
diff --git a/arch/x86/include/asm/vmware.h b/arch/x86/include/asm/vmware.h
new file mode 100644 (file)
index 0000000..02dfea5
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2008, VMware, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef ASM_X86__VMWARE_H
+#define ASM_X86__VMWARE_H
+
+extern unsigned long vmware_get_tsc_khz(void);
+extern int vmware_platform(void);
+
+#endif
index 82ec6075c057b695cf6904a28a58fb8c60c37d7a..a5c04e88777e84fcabeb46c67c4fb4941a6261f4 100644 (file)
@@ -4,6 +4,7 @@
 
 obj-y                  := intel_cacheinfo.o addon_cpuid_features.o
 obj-y                  += proc.o capflags.o powerflags.o common.o
+obj-y                  += vmware.o hypervisor.o
 
 obj-$(CONFIG_X86_32)   += bugs.o cmpxchg.o
 obj-$(CONFIG_X86_64)   += bugs_64.o
index b9c9ea0217a9b1fd4581f27fed692aaa6fe167ee..b88595c36254d8e54e03564e929ada40d8bb03f0 100644 (file)
@@ -36,6 +36,7 @@
 #include <asm/proto.h>
 #include <asm/sections.h>
 #include <asm/setup.h>
+#include <asm/hypervisor.h>
 
 #include "cpu.h"
 
@@ -703,6 +704,7 @@ static void __cpuinit identify_cpu(struct cpuinfo_x86 *c)
        detect_ht(c);
 #endif
 
+       init_hypervisor(c);
        /*
         * On SMP, boot_cpu_data holds the common feature set between
         * all CPUs; so make sure that we indicate which features are
diff --git a/arch/x86/kernel/cpu/hypervisor.c b/arch/x86/kernel/cpu/hypervisor.c
new file mode 100644 (file)
index 0000000..7bd5506
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Common hypervisor code
+ *
+ * Copyright (C) 2008, VMware, Inc.
+ * Author : Alok N Kataria <akataria@vmware.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <asm/processor.h>
+#include <asm/vmware.h>
+
+static inline void __cpuinit
+detect_hypervisor_vendor(struct cpuinfo_x86 *c)
+{
+       if (vmware_platform()) {
+               c->x86_hyper_vendor = X86_HYPER_VENDOR_VMWARE;
+       } else {
+               c->x86_hyper_vendor = X86_HYPER_VENDOR_NONE;
+       }
+}
+
+unsigned long get_hypervisor_tsc_freq(void)
+{
+       if (boot_cpu_data.x86_hyper_vendor == X86_HYPER_VENDOR_VMWARE)
+               return vmware_get_tsc_khz();
+       return 0;
+}
+
+void __cpuinit init_hypervisor(struct cpuinfo_x86 *c)
+{
+       detect_hypervisor_vendor(c);
+}
+
diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c
new file mode 100644 (file)
index 0000000..d5d1b75
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * VMware Detection code.
+ *
+ * Copyright (C) 2008, VMware, Inc.
+ * Author : Alok N Kataria <akataria@vmware.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <linux/dmi.h>
+#include <asm/div64.h>
+
+#define CPUID_VMWARE_INFO_LEAF 0x40000000
+#define VMWARE_HYPERVISOR_MAGIC        0x564D5868
+#define VMWARE_HYPERVISOR_PORT 0x5658
+
+#define VMWARE_PORT_CMD_GETVERSION     10
+#define VMWARE_PORT_CMD_GETHZ          45
+
+#define VMWARE_PORT(cmd, eax, ebx, ecx, edx)                           \
+       __asm__("inl (%%dx)" :                                          \
+                       "=a"(eax), "=c"(ecx), "=d"(edx), "=b"(ebx) :    \
+                       "0"(VMWARE_HYPERVISOR_MAGIC),                   \
+                       "1"(VMWARE_PORT_CMD_##cmd),                     \
+                       "2"(VMWARE_HYPERVISOR_PORT), "3"(0) :           \
+                       "memory");
+
+static inline int __vmware_platform(void)
+{
+       uint32_t eax, ebx, ecx, edx;
+       VMWARE_PORT(GETVERSION, eax, ebx, ecx, edx);
+       return eax != (uint32_t)-1 && ebx == VMWARE_HYPERVISOR_MAGIC;
+}
+
+static unsigned long __vmware_get_tsc_khz(void)
+{
+        uint64_t tsc_hz;
+        uint32_t eax, ebx, ecx, edx;
+
+        VMWARE_PORT(GETHZ, eax, ebx, ecx, edx);
+
+        if (eax == (uint32_t)-1)
+                return 0;
+        tsc_hz = eax | (((uint64_t)ebx) << 32);
+        do_div(tsc_hz, 1000);
+        BUG_ON(tsc_hz >> 32);
+        return tsc_hz;
+}
+
+int vmware_platform(void)
+{
+       if (cpu_has_hypervisor) {
+               unsigned int eax, ebx, ecx, edx;
+               char hyper_vendor_id[13];
+
+               cpuid(CPUID_VMWARE_INFO_LEAF, &eax, &ebx, &ecx, &edx);
+               memcpy(hyper_vendor_id + 0, &ebx, 4);
+               memcpy(hyper_vendor_id + 4, &ecx, 4);
+               memcpy(hyper_vendor_id + 8, &edx, 4);
+               hyper_vendor_id[12] = '\0';
+               if (!strcmp(hyper_vendor_id, "VMwareVMware"))
+                       return 1;
+       } else if (dmi_available && dmi_name_in_vendors("VMware") &&
+                  __vmware_platform())
+               return 1;
+
+       return 0;
+}
+
+unsigned long vmware_get_tsc_khz(void)
+{
+       BUG_ON(!vmware_platform());
+       return __vmware_get_tsc_khz();
+}
index 0fa6790c1dd37d76e257de661ba3ed9312de89e0..f44dadfb32cf3b87082c8909a90e57a8fd02c103 100644 (file)
@@ -98,6 +98,7 @@
 
 #include <mach_apic.h>
 #include <asm/paravirt.h>
+#include <asm/hypervisor.h>
 
 #include <asm/percpu.h>
 #include <asm/topology.h>
@@ -909,6 +910,12 @@ void __init setup_arch(char **cmdline_p)
 
        dmi_check_system(bad_bios_dmi_table);
 
+       /*
+        * VMware detection requires dmi to be available, so this
+        * needs to be done after dmi_scan_machine, for the BP.
+        */
+       init_hypervisor(&boot_cpu_data);
+
 #ifdef CONFIG_X86_32
        probe_roms();
 #endif
index 62348e4fd8d1e6af29d04726499b13794a78949e..6dbf0bcb44a8bc2d56b6778aa12a22484e7f691b 100644 (file)
@@ -15,6 +15,7 @@
 #include <asm/vgtod.h>
 #include <asm/time.h>
 #include <asm/delay.h>
+#include <asm/hypervisor.h>
 
 unsigned int cpu_khz;           /* TSC clocks / usec, not used here */
 EXPORT_SYMBOL(cpu_khz);
@@ -352,9 +353,15 @@ unsigned long native_calibrate_tsc(void)
 {
        u64 tsc1, tsc2, delta, ref1, ref2;
        unsigned long tsc_pit_min = ULONG_MAX, tsc_ref_min = ULONG_MAX;
-       unsigned long flags, latch, ms, fast_calibrate;
+       unsigned long flags, latch, ms, fast_calibrate, tsc_khz;
        int hpet = is_hpet_enabled(), i, loopmin;
 
+       tsc_khz = get_hypervisor_tsc_freq();
+       if (tsc_khz) {
+               printk(KERN_INFO "TSC: Frequency read from the hypervisor\n");
+               return tsc_khz;
+       }
+
        local_irq_save(flags);
        fast_calibrate = quick_pit_calibrate();
        local_irq_restore(flags);