From 064e81d2291c9b410093e5d552d4ecf7d6e582c5 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Tue, 4 Mar 2014 19:07:21 +0100
Subject: [PATCH] kernel: Add more detailed VM detection.

Previously, the kernel global 'vmm_guest' was either 0 or 1 depending
on the VMM bit in the CPU features (which isn't set in all VMs).

This commit adds more detailed information by checking the emulated
BIOS for known strings. The detected VMs include vkernel, which
doesn't strictly fit into the category, but it shares enough
similarities for this to be useful.

Also expose this information in a read-only sysctl (kern.vmm_guest).

The detection code was kind of adapted from FreeBSD (although their
kern.vm_guest works differently).

Tested-by: tuxillo
---
 sys/kern/subr_param.c                  | 89 +++++++++++++++++++++++++++++++++-
 sys/platform/pc32/i386/initcpu.c       |  6 ++-
 sys/platform/pc64/x86_64/initcpu.c     |  9 ++--
 sys/platform/vkernel/platform/init.c   |  2 +-
 sys/platform/vkernel64/platform/init.c |  2 +-
 sys/sys/kernel.h                       | 17 +++++++
 sys/sys/systm.h                        |  1 +
 7 files changed, 118 insertions(+), 8 deletions(-)

diff --git a/sys/kern/subr_param.c b/sys/kern/subr_param.c
index af9ad46..68cefba 100644
--- a/sys/kern/subr_param.c
+++ b/sys/kern/subr_param.c
@@ -33,7 +33,6 @@
  *
  *	@(#)param.c	8.3 (Berkeley) 8/20/94
  * $FreeBSD: src/sys/kern/subr_param.c,v 1.42.2.10 2002/03/09 21:05:47 silby Exp $
- * $DragonFly: src/sys/kern/subr_param.c,v 1.7 2005/06/26 22:03:22 dillon Exp $
  */
 
 #include "opt_param.h"
@@ -43,6 +42,7 @@
 #include <sys/systm.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
+#include <sys/sysctl.h>
 #include <vm/pmap.h>
 #include <machine/vmparam.h>
 
@@ -64,6 +64,8 @@
 #define MAXPOSIXLOCKSPERUID (maxusers * 64) /* Should be a safe value */
 #endif
 
+static int sysctl_kern_vmm_guest(SYSCTL_HANDLER_ARGS);
+
 int	hz;
 int	stathz;
 int	profhz;
@@ -92,6 +94,10 @@ u_quad_t	dflssiz;			/* initial stack size limit */
 u_quad_t	maxssiz;			/* max stack size */
 u_quad_t	sgrowsiz;			/* amount to grow stack */
 
+SYSCTL_PROC(_kern, OID_AUTO, vmm_guest, CTLFLAG_RD | CTLTYPE_STRING,
+    NULL, 0, sysctl_kern_vmm_guest, "A",
+    "Virtual machine guest type");
+
 /*
  * These have to be allocated somewhere; allocating
  * them here forces loader errors if this file is omitted
@@ -99,6 +105,78 @@ u_quad_t	sgrowsiz;			/* amount to grow stack */
  */
 struct	buf *swbuf;
 
+struct vmm_bname {
+	const char *str;
+	enum vmm_guest_type type;
+};
+
+static struct vmm_bname vmm_bnames[] = {
+	{ "QEMU",	VMM_GUEST_QEMU },	/* QEMU */
+	{ "Plex86",	VMM_GUEST_PLEX86 },	/* Plex86 */
+	{ "Bochs",	VMM_GUEST_BOCHS },	/* Bochs */
+	{ "Xen",	VMM_GUEST_XEN },	/* Xen */
+	{ "BHYVE",	VMM_GUEST_BHYVE },	/* bhyve */
+	{ "Seabios",	VMM_GUEST_KVM},		/* KVM */
+	{ NULL, 0 }
+};
+
+static struct vmm_bname vmm_pnames[] = {
+	{ "VMware Virtual Platform",	VMM_GUEST_VMWARE },	/* VMWare VM */
+	{ "Virtual Machine",		VMM_GUEST_VPC },	/* M$ VirtualPC */
+	{ "VirtualBox",			VMM_GUEST_VBOX },	/* Sun VirtualBox */
+	{ "Parallels Virtual Platform",	VMM_GUEST_PARALLELS },	/* Parallels VM */
+	{ "KVM",			VMM_GUEST_KVM },	/* KVM */
+	{ NULL, 0 }
+};
+
+static const char *const vmm_guest_sysctl_names[] = {
+	"none",
+	"qemu",
+	"plex86",
+	"bochs",
+	"xen",
+	"bhyve",
+	"kvm",
+	"vmware",
+	"vpc",
+	"vbox",
+	"parallels",
+	"vkernel",
+	"unknown",
+	NULL
+};
+CTASSERT(NELEM(vmm_guest_sysctl_names) - 1 == VMM_LAST);
+
+/*
+ * Detect known Virtual Machine hosts by inspecting the emulated BIOS.
+ */
+enum vmm_guest_type
+detect_virtual(void)
+{
+	char *sysenv;
+	int i;
+
+	sysenv = kgetenv("smbios.bios.vendor");
+	if (sysenv != NULL) {
+		for (i = 0; vmm_bnames[i].str != NULL; i++)
+			if (strcmp(sysenv, vmm_bnames[i].str) == 0) {
+				kfreeenv(sysenv);
+				return (vmm_bnames[i].type);
+			}
+		kfreeenv(sysenv);
+	}
+	sysenv = kgetenv("smbios.system.product");
+	if (sysenv != NULL) {
+		for (i = 0; vmm_pnames[i].str != NULL; i++)
+			if (strcmp(sysenv, vmm_pnames[i].str) == 0) {
+				kfreeenv(sysenv);
+				return (vmm_pnames[i].type);
+			}
+		kfreeenv(sysenv);
+	}
+	return (VMM_GUEST_NONE);
+}
+
 /*
  * Boot time overrides that are not scaled against main memory
  */
@@ -224,3 +302,12 @@ init_param2(int physpages)
 	TUNABLE_INT_FETCH("kern.ncallout", &ncallout);
 }
 
+/*
+ * Sysctl stringifying handler for kern.vmm_guest.
+ */
+static int
+sysctl_kern_vmm_guest(SYSCTL_HANDLER_ARGS)
+{
+	return (SYSCTL_OUT(req, vmm_guest_sysctl_names[vmm_guest], 
+	    strlen(vmm_guest_sysctl_names[vmm_guest])));
+}
diff --git a/sys/platform/pc32/i386/initcpu.c b/sys/platform/pc32/i386/initcpu.c
index 6df43b5..1ffe84d 100644
--- a/sys/platform/pc32/i386/initcpu.c
+++ b/sys/platform/pc32/i386/initcpu.c
@@ -359,8 +359,10 @@ initializecpu(void)
 	}
 	enable_sse();
 
-	if (cpu_feature2 & CPUID2_VMM)
-		vmm_guest = 1;
+	/* Check if we are running in a hypervisor. */
+	vmm_guest = detect_virtual();
+	if (vmm_guest == VMM_GUEST_NONE && (cpu_feature2 & CPUID2_VMM))
+		vmm_guest = VMM_GUEST_UNKNOWN;
 
 	if (cpu_vendor_id == CPU_VENDOR_AMD) {
 		switch((cpu_id & 0xFF0000)) {
diff --git a/sys/platform/pc64/x86_64/initcpu.c b/sys/platform/pc64/x86_64/initcpu.c
index 6893531..b597032 100644
--- a/sys/platform/pc64/x86_64/initcpu.c
+++ b/sys/platform/pc64/x86_64/initcpu.c
@@ -163,9 +163,12 @@ initializecpu(int cpu)
 		cpu_fxsr = hw_instruction_sse = 1;
 	}
 
-	/* Check if we are running in a hypervisor. */
-	if (cpu_feature2 & CPUID2_VMM)
-		vmm_guest = 1;
+	if (cpu == 0) {
+		/* Check if we are running in a hypervisor. */
+		vmm_guest = detect_virtual();
+		if (vmm_guest == VMM_GUEST_NONE && (cpu_feature2 & CPUID2_VMM))
+			vmm_guest = VMM_GUEST_UNKNOWN;
+	}
 
 #if !defined(CPU_DISABLE_AVX)
 	/*Check for XSAVE and AVX support and enable if available.*/
diff --git a/sys/platform/vkernel/platform/init.c b/sys/platform/vkernel/platform/init.c
index bbb1643..eda6a75 100644
--- a/sys/platform/vkernel/platform/init.c
+++ b/sys/platform/vkernel/platform/init.c
@@ -369,7 +369,7 @@ main(int ac, char **av)
 	setrealcpu();
 	init_kqueue();
 
-	vmm_guest = 1;
+	vmm_guest = VMM_GUEST_VKERNEL;
 
 	/*
 	 * Check TSC
diff --git a/sys/platform/vkernel64/platform/init.c b/sys/platform/vkernel64/platform/init.c
index d27d2a4..adbbbe5 100644
--- a/sys/platform/vkernel64/platform/init.c
+++ b/sys/platform/vkernel64/platform/init.c
@@ -422,7 +422,7 @@ int main(int ac, char **av) {
 	setrealcpu();
 	init_kqueue();
 
-	vmm_guest = 1;
+	vmm_guest = VMM_GUEST_VKERNEL;
 
 	/*
 	 * Check TSC
diff --git a/sys/sys/kernel.h b/sys/sys/kernel.h
index 7347f9e..8d8a340 100644
--- a/sys/sys/kernel.h
+++ b/sys/sys/kernel.h
@@ -87,6 +87,23 @@ extern int lbolt;			/* once a second sleep address */
 extern void *lbolt_syncer;		/* approx 1 hz but may be sped up */
 extern int vmm_guest; 			/* Running as virtual machine guest? */
 
+enum vmm_guest_type {
+	VMM_GUEST_NONE = 0,
+	VMM_GUEST_QEMU,
+	VMM_GUEST_PLEX86,
+	VMM_GUEST_BOCHS,
+	VMM_GUEST_XEN,
+	VMM_GUEST_BHYVE,
+	VMM_GUEST_KVM,
+	VMM_GUEST_VMWARE,
+	VMM_GUEST_VPC,
+	VMM_GUEST_VBOX,
+	VMM_GUEST_PARALLELS,
+	VMM_GUEST_VKERNEL,
+	VMM_GUEST_UNKNOWN,
+	VMM_LAST
+};
+
 /*
  * Enumerated types for known system startup interfaces.
  *
diff --git a/sys/sys/systm.h b/sys/sys/systm.h
index ac94cbe..d0926ea 100644
--- a/sys/sys/systm.h
+++ b/sys/sys/systm.h
@@ -169,6 +169,7 @@ void	cpu_rootconf (void);
 void	cpu_vmspace_alloc(struct vmspace *);
 void	cpu_vmspace_free(struct vmspace *);
 void	cpu_vkernel_trap(struct trapframe *, int);
+enum vmm_guest_type detect_virtual(void);
 void	set_user_TLS(void);
 void	set_vkernel_fp(struct trapframe *);
 int	kvm_access_check(vm_offset_t, vm_offset_t, int);
-- 
1.8.2.2

