xref: /illumos-gate/usr/src/uts/i86pc/os/tscc_vmware.c (revision 2833423dc59f4c35fe4713dbb942950c82df0437)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2021 Jason King
14  * Copyright 2024 Oxide Computer Company
15  */
16 
17 #include <sys/x86_archext.h>
18 #include <sys/tsc.h>
19 
20 /* For VMs, this leaf will contain the largest VM leaf supported in EAX */
21 #define	CPUID_VM_LEAF_MAX	0x40000000
22 
23 /*
24  * From https://lwn.net/Articles/301888/:
25  * CPUID leaf 0x40000010 (when present on supported VMMs) will contain the TSC
26  * frequency in kHz.
27  */
28 #define	CPUID_VM_LEAF_FREQ	0x40000010
29 
30 /*
31  * These get_hwenv() types correspond to the platforms which are known to have
32  * support for exposing the TSC frequency via the aforementioned leaf.
33  */
34 #define	HW_SUPPORTS_FREQ	(HW_VMWARE | HW_KVM | HW_VIRTUALBOX | HW_ACRN)
35 
36 /*
37  * Allow bypassing the platform identification step when trying to determine if
38  * the host has support for the VM frequency leaf.  This allows use of the leaf
39  * on hypervisors which are otherwise unknown.
40  */
41 int tscc_vmware_match_any = 0;
42 
43 static boolean_t
44 tsc_calibrate_vmware(uint64_t *freqp)
45 {
46 	struct cpuid_regs regs = { 0 };
47 
48 	/*
49 	 * Are we on a platform with support?  (or has the administrator
50 	 * expressed their intent to bypass this check via the config option.)
51 	 */
52 	if ((get_hwenv() & HW_SUPPORTS_FREQ) == 0 &&
53 	    tscc_vmware_match_any == 0) {
54 		return (B_FALSE);
55 	}
56 
57 	/* ... And does it expose up through the required leaf? */
58 	regs.cp_eax = CPUID_VM_LEAF_MAX;
59 	__cpuid_insn(&regs);
60 	if (regs.cp_eax < CPUID_VM_LEAF_FREQ) {
61 		return (B_FALSE);
62 	}
63 
64 	regs.cp_eax = CPUID_VM_LEAF_FREQ;
65 	__cpuid_insn(&regs);
66 
67 	/*
68 	 * While not observed in the wild, as a precautionary measure,
69 	 * we treat a value of 0 as a failure out of an excess of caution.
70 	 */
71 	if (regs.cp_eax == 0) {
72 		return (B_FALSE);
73 	}
74 
75 	/* Convert from kHz to Hz */
76 	*freqp = (uint64_t)regs.cp_eax * 1000;
77 
78 	return (B_TRUE);
79 }
80 
81 static tsc_calibrate_t tsc_calibration_vmware = {
82 	.tscc_source = "VMware",
83 	.tscc_preference = 100,
84 	.tscc_calibrate = tsc_calibrate_vmware,
85 };
86 TSC_CALIBRATION_SOURCE(tsc_calibration_vmware);
87