Rework virtual machine hypervisor detection.

- Move the existing code to x86/x86/identcpu.c since it is x86-specific.
- If the CPUID2_HV flag is set, assume a hypervisor is present and query
  the 0x40000000 leaf to determine the hypervisor vendor ID.  Export the
  vendor ID and the highest supported hypervisor CPUID leaf via
  hv_vendor[] and hv_high variables, respectively.  The hv_vendor[]
  array is also exported via the hw.hv_vendor sysctl.
- Merge the VMWare detection code from tsc.c into the new probe in
  identcpu.c.  Add a VM_GUEST_VMWARE to identify vmware and use that in
  the TSC code to identify VMWare.

Differential Revision:	https://reviews.freebsd.org/D1010
Reviewed by:	delphij, jkim, neel
This commit is contained in:
John Baldwin 2014-10-28 19:17:44 +00:00
parent 8839e0e9f3
commit 01e1933dcc
7 changed files with 187 additions and 134 deletions

View file

@ -62,6 +62,8 @@ extern u_int cpu_mon_mwait_flags;
extern u_int cpu_mon_min_size;
extern u_int cpu_mon_max_size;
extern char ctx_switch_xsave[];
extern u_int hv_high;
extern char hv_vendor[];
extern char kstack[];
extern char sigcode[];
extern int szsigcode;

View file

@ -64,6 +64,8 @@ extern u_int cyrix_did;
#if defined(I586_CPU) && !defined(NO_F00F_HACK)
extern int has_f00f_bug;
#endif
extern u_int hv_high;
extern char hv_vendor[];
extern char kstack[];
extern char sigcode[];
extern int szsigcode;

View file

@ -99,7 +99,11 @@ pid_t pid_max = PID_MAX;
long maxswzone; /* max swmeta KVA storage */
long maxbcache; /* max buffer cache KVA storage */
long maxpipekva; /* Limit on pipe KVA */
int vm_guest; /* Running as virtual machine guest? */
#ifdef XEN
int vm_guest = VM_GUEST_XEN;
#else
int vm_guest = VM_GUEST_NO; /* Running as virtual machine guest? */
#endif
u_long maxtsiz; /* max text size */
u_long dfldsiz; /* initial data size limit */
u_long maxdsiz; /* max data size */
@ -136,7 +140,7 @@ SYSCTL_ULONG(_kern, OID_AUTO, sgrowsiz, CTLFLAG_RWTUN | CTLFLAG_NOFETCH, &sgrows
"Amount to grow stack on a stack fault");
SYSCTL_PROC(_kern, OID_AUTO, vm_guest, CTLFLAG_RD | CTLTYPE_STRING,
NULL, 0, sysctl_kern_vm_guest, "A",
"Virtual machine guest detected? (none|generic|xen)");
"Virtual machine guest detected?");
/*
* These have to be allocated somewhere; allocating
@ -154,73 +158,18 @@ static const char *const vm_guest_sysctl_names[] = {
"generic",
"xen",
"hv",
"vmware",
NULL
};
CTASSERT(nitems(vm_guest_sysctl_names) - 1 == VM_LAST);
#ifndef XEN
static const char *const vm_bnames[] = {
"QEMU", /* QEMU */
"Plex86", /* Plex86 */
"Bochs", /* Bochs */
"Xen", /* Xen */
"BHYVE", /* bhyve */
"Seabios", /* KVM */
NULL
};
static const char *const vm_pnames[] = {
"VMware Virtual Platform", /* VMWare VM */
"Virtual Machine", /* Microsoft VirtualPC */
"VirtualBox", /* Sun xVM VirtualBox */
"Parallels Virtual Platform", /* Parallels VM */
"KVM", /* KVM */
NULL
};
/*
* Detect known Virtual Machine hosts by inspecting the emulated BIOS.
*/
static enum VM_GUEST
detect_virtual(void)
{
char *sysenv;
int i;
sysenv = kern_getenv("smbios.bios.vendor");
if (sysenv != NULL) {
for (i = 0; vm_bnames[i] != NULL; i++)
if (strcmp(sysenv, vm_bnames[i]) == 0) {
freeenv(sysenv);
return (VM_GUEST_VM);
}
freeenv(sysenv);
}
sysenv = kern_getenv("smbios.system.product");
if (sysenv != NULL) {
for (i = 0; vm_pnames[i] != NULL; i++)
if (strcmp(sysenv, vm_pnames[i]) == 0) {
freeenv(sysenv);
return (VM_GUEST_VM);
}
freeenv(sysenv);
}
return (VM_GUEST_NO);
}
#endif
/*
* Boot time overrides that are not scaled against main memory
*/
void
init_param1(void)
{
#ifndef XEN
vm_guest = detect_virtual();
#else
vm_guest = VM_GUEST_XEN;
#endif
hz = -1;
TUNABLE_INT_FETCH("kern.hz", &hz);
if (hz == -1)

View file

@ -73,7 +73,7 @@ extern int vm_guest; /* Running as virtual machine guest? */
* Keep in sync with vm_guest_sysctl_names[].
*/
enum VM_GUEST { VM_GUEST_NO = 0, VM_GUEST_VM, VM_GUEST_XEN, VM_GUEST_HV,
VM_LAST };
VM_GUEST_VMWARE, VM_LAST };
#if defined(WITNESS) || defined(INVARIANTS)
void kassert_panic(const char *fmt, ...) __printflike(1, 2);

47
sys/x86/include/vmware.h Normal file
View file

@ -0,0 +1,47 @@
/*-
* Copyright (c) 2011-2014 Jung-uk Kim <jkim@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _X86_VMWARE_H_
#define _X86_VMWARE_H_
#define VMW_HVMAGIC 0x564d5868
#define VMW_HVPORT 0x5658
#define VMW_HVCMD_GETVERSION 10
#define VMW_HVCMD_GETHZ 45
static __inline void
vmware_hvcall(u_int cmd, u_int *p)
{
__asm __volatile("inl %w3, %0"
: "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
: "0" (VMW_HVMAGIC), "1" (UINT_MAX), "2" (cmd), "3" (VMW_HVPORT)
: "memory");
}
#endif /* !_X86_VMWARE_H_ */

View file

@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/eventhandler.h>
#include <sys/limits.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
@ -63,6 +64,7 @@ __FBSDID("$FreeBSD$");
#include <amd64/vmm/intel/vmx_controls.h>
#include <x86/isa/icu.h>
#include <x86/vmware.h>
#ifdef __i386__
#define IDENTBLUE_CYRIX486 0
@ -76,6 +78,7 @@ static u_int find_cpu_vendor_id(void);
static void print_AMD_info(void);
static void print_INTEL_info(void);
static void print_INTEL_TLB(u_int data);
static void print_hypervisor_info(void);
static void print_svm_info(void);
static void print_via_padlock_info(void);
static void print_vmx_info(void);
@ -120,6 +123,11 @@ static int hw_clockrate;
SYSCTL_INT(_hw, OID_AUTO, clockrate, CTLFLAG_RD,
&hw_clockrate, 0, "CPU instruction clock rate");
u_int hv_high;
char hv_vendor[16];
SYSCTL_STRING(_hw, OID_AUTO, hv_vendor, CTLFLAG_RD, hv_vendor, 0,
"Hypervisor vendor");
static eventhandler_tag tsc_post_tag;
static char cpu_brand[48];
@ -949,7 +957,6 @@ printcpuinfo(void)
if (tsc_perf_stat)
printf(", performance statistics");
}
}
#ifdef __i386__
} else if (cpu_vendor_id == CPU_VENDOR_CYRIX) {
@ -967,17 +974,18 @@ printcpuinfo(void)
if (*cpu_vendor || cpu_id)
printf("\n");
if (!bootverbose)
return;
if (cpu_vendor_id == CPU_VENDOR_AMD)
print_AMD_info();
else if (cpu_vendor_id == CPU_VENDOR_INTEL)
print_INTEL_info();
if (bootverbose) {
if (cpu_vendor_id == CPU_VENDOR_AMD)
print_AMD_info();
else if (cpu_vendor_id == CPU_VENDOR_INTEL)
print_INTEL_info();
#ifdef __i386__
else if (cpu_vendor_id == CPU_VENDOR_TRANSMETA)
print_transmeta_info();
else if (cpu_vendor_id == CPU_VENDOR_TRANSMETA)
print_transmeta_info();
#endif
}
print_hypervisor_info();
}
void
@ -1182,6 +1190,99 @@ hook_tsc_freq(void *arg __unused)
SYSINIT(hook_tsc_freq, SI_SUB_CONFIGURE, SI_ORDER_ANY, hook_tsc_freq, NULL);
#ifndef XEN
static const char *const vm_bnames[] = {
"QEMU", /* QEMU */
"Plex86", /* Plex86 */
"Bochs", /* Bochs */
"Xen", /* Xen */
"BHYVE", /* bhyve */
"Seabios", /* KVM */
NULL
};
static const char *const vm_pnames[] = {
"VMware Virtual Platform", /* VMWare VM */
"Virtual Machine", /* Microsoft VirtualPC */
"VirtualBox", /* Sun xVM VirtualBox */
"Parallels Virtual Platform", /* Parallels VM */
"KVM", /* KVM */
NULL
};
static void
identify_hypervisor(void)
{
u_int regs[4];
char *p;
int i;
/*
* [RFC] CPUID usage for interaction between Hypervisors and Linux.
* http://lkml.org/lkml/2008/10/1/246
*
* KB1009458: Mechanisms to determine if software is running in
* a VMware virtual machine
* http://kb.vmware.com/kb/1009458
*/
if (cpu_feature2 & CPUID2_HV) {
vm_guest = VM_GUEST_VM;
do_cpuid(0x40000000, regs);
if (regs[0] >= 0x40000000) {
hv_high = regs[0];
((u_int *)&hv_vendor)[0] = regs[1];
((u_int *)&hv_vendor)[1] = regs[2];
((u_int *)&hv_vendor)[2] = regs[3];
hv_vendor[12] = '\0';
if (strcmp(hv_vendor, "VMwareVMware") == 0)
vm_guest = VM_GUEST_VMWARE;
}
return;
}
/*
* Examine SMBIOS strings for older hypervisors.
*/
p = kern_getenv("smbios.system.serial");
if (p != NULL) {
if (strncmp(p, "VMware-", 7) == 0 || strncmp(p, "VMW", 3) == 0) {
vmware_hvcall(VMW_HVCMD_GETVERSION, regs);
if (regs[1] == VMW_HVMAGIC) {
vm_guest = VM_GUEST_VMWARE;
freeenv(p);
return;
}
}
freeenv(p);
}
/*
* XXX: Some of these entries may not be needed since they were
* added to FreeBSD before the checks above.
*/
p = kern_getenv("smbios.bios.vendor");
if (p != NULL) {
for (i = 0; vm_bnames[i] != NULL; i++)
if (strcmp(p, vm_bnames[i]) == 0) {
vm_guest = VM_GUEST_VM;
freeenv(p);
return;
}
freeenv(p);
}
p = kern_getenv("smbios.system.product");
if (p != NULL) {
for (i = 0; vm_pnames[i] != NULL; i++)
if (strcmp(p, vm_pnames[i]) == 0) {
vm_guest = VM_GUEST_VM;
freeenv(p);
return;
}
freeenv(p);
}
}
#endif
/*
* Final stage of CPU identification.
*/
@ -1213,6 +1314,9 @@ identify_cpu(void)
cpu_feature2 = regs[2];
#endif
#ifndef XEN
identify_hypervisor();
#endif
cpu_vendor_id = find_cpu_vendor_id();
/*
@ -2046,3 +2150,11 @@ print_vmx_info(void)
);
}
}
static void
print_hypervisor_info(void)
{
if (*hv_vendor)
printf("Hypervisor: Origin = \"%s\"\n", hv_vendor);
}

View file

@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
#include <machine/cputypes.h>
#include <machine/md_var.h>
#include <machine/specialreg.h>
#include <x86/vmware.h>
#include "cpufreq_if.h"
@ -102,72 +103,11 @@ static struct timecounter tsc_timecounter = {
800, /* quality (adjusted in code) */
};
#define VMW_HVMAGIC 0x564d5868
#define VMW_HVPORT 0x5658
#define VMW_HVCMD_GETVERSION 10
#define VMW_HVCMD_GETHZ 45
static __inline void
vmware_hvcall(u_int cmd, u_int *p)
{
__asm __volatile("inl %w3, %0"
: "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
: "0" (VMW_HVMAGIC), "1" (UINT_MAX), "2" (cmd), "3" (VMW_HVPORT)
: "memory");
}
static int
static void
tsc_freq_vmware(void)
{
char hv_sig[13];
u_int regs[4];
char *p;
u_int hv_high;
int i;
/*
* [RFC] CPUID usage for interaction between Hypervisors and Linux.
* http://lkml.org/lkml/2008/10/1/246
*
* KB1009458: Mechanisms to determine if software is running in
* a VMware virtual machine
* http://kb.vmware.com/kb/1009458
*/
hv_high = 0;
if ((cpu_feature2 & CPUID2_HV) != 0) {
do_cpuid(0x40000000, regs);
hv_high = regs[0];
for (i = 1, p = hv_sig; i < 4; i++, p += sizeof(regs) / 4)
memcpy(p, &regs[i], sizeof(regs[i]));
*p = '\0';
if (bootverbose) {
/*
* HV vendor ID string
* ------------+--------------
* KVM "KVMKVMKVM"
* Microsoft "Microsoft Hv"
* VMware "VMwareVMware"
* Xen "XenVMMXenVMM"
*/
printf("Hypervisor: Origin = \"%s\"\n", hv_sig);
}
if (strncmp(hv_sig, "VMwareVMware", 12) != 0)
return (0);
} else {
p = kern_getenv("smbios.system.serial");
if (p == NULL)
return (0);
if (strncmp(p, "VMware-", 7) != 0 &&
strncmp(p, "VMW", 3) != 0) {
freeenv(p);
return (0);
}
freeenv(p);
vmware_hvcall(VMW_HVCMD_GETVERSION, regs);
if (regs[1] != VMW_HVMAGIC)
return (0);
}
if (hv_high >= 0x40000010) {
do_cpuid(0x40000010, regs);
tsc_freq = regs[0] * 1000;
@ -177,7 +117,6 @@ tsc_freq_vmware(void)
tsc_freq = regs[0] | ((uint64_t)regs[1] << 32);
}
tsc_is_invariant = 1;
return (1);
}
static void
@ -261,8 +200,10 @@ probe_tsc_freq(void)
}
}
if (tsc_freq_vmware())
if (vm_guest == VM_GUEST_VMWARE) {
tsc_freq_vmware();
return;
}
switch (cpu_vendor_id) {
case CPU_VENDOR_AMD: