whpx: i386: wire up feature probing
Windows 10 doesn't have the API for this, so using this only for Windows 11. Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr> Link: https://lore.kernel.org/r/20260422214225.2242-4-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Mohamed Mediouni committed
Apr 22, 2026 at 23:41 UTC
057143ba75142fd3fd2f1b1295d73e4ff61f8736
6 files changed
+370
-5
include/system/whpx-internal.h
+9
@@ -73,6 +73,14 @@ void whpx_apic_get(APICCommonState *s);
73
X(HRESULT, WHvGetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, WHV_REGISTER_VALUE* RegisterValues)) \
74
X(HRESULT, WHvSetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, const WHV_REGISTER_VALUE* RegisterValues)) \
75
76
+#ifdef __x86_64__
77
+#define LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X) \
78
+ X(HRESULT, WHvGetVirtualProcessorCpuidOutput, \
79
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, UINT32 Eax, \
80
+ UINT32 Ecx, WHV_CPUID_OUTPUT *CpuidOutput))
81
+#else
82
+#define LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X)
83
+#endif
84
/*
85
* These are supplemental functions that may not be present
86
* on all versions and are not critical for basic functionality.
@@ -89,6 +97,7 @@ void whpx_apic_get(APICCommonState *s);
97
UINT32 StateSize)) \
98
X(HRESULT, WHvResetPartition, \
99
(WHV_PARTITION_HANDLE Partition)) \
100
+ LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X)
101
102
#define WHP_DEFINE_TYPE(return_type, function_name, signature) \
103
typedef return_type (WINAPI *function_name ## _t) signature;
target/i386/cpu.c
+17
@@ -26,6 +26,8 @@
26
#include "tcg/helper-tcg.h"
27
#include "exec/translation-block.h"
28
#include "system/hvf.h"
29
+#include "system/whpx.h"
30
+#include "whpx/whpx-i386.h"
31
#include "hvf/hvf-i386.h"
32
#include "kvm/kvm_i386.h"
33
#include "kvm/tdx.h"
@@ -8230,6 +8232,16 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w)
8232
r = hvf_get_supported_cpuid(wi->cpuid.eax,
8233
wi->cpuid.ecx,
8234
wi->cpuid.reg);
8235
+ } else if (whpx_enabled()) {
8236
+ switch (wi->type) {
8237
+ case CPUID_FEATURE_WORD:
8238
+ r = whpx_get_supported_cpuid(wi->cpuid.eax, wi->cpuid.ecx,
8239
+ wi->cpuid.reg);
8240
+ break;
8241
+ case MSR_FEATURE_WORD:
8242
+ r = whpx_get_supported_msr_feature(wi->msr.index);
8243
+ break;
8244
+ }
8245
} else if (tcg_enabled() || qtest_enabled()) {
8246
r = wi->tcg_features;
8247
} else {
@@ -8311,6 +8323,11 @@ static void x86_cpu_get_supported_cpuid(uint32_t func, uint32_t index,
8323
*ebx = hvf_get_supported_cpuid(func, index, R_EBX);
8324
*ecx = hvf_get_supported_cpuid(func, index, R_ECX);
8325
*edx = hvf_get_supported_cpuid(func, index, R_EDX);
8326
+ } else if (whpx_enabled()) {
8327
+ *eax = whpx_get_supported_cpuid(func, index, R_EAX);
8328
+ *ebx = whpx_get_supported_cpuid(func, index, R_EBX);
8329
+ *ecx = whpx_get_supported_cpuid(func, index, R_ECX);
8330
+ *edx = whpx_get_supported_cpuid(func, index, R_EDX);
8331
} else {
8332
*eax = 0;
8333
*ebx = 0;
target/i386/whpx/meson.build
+1
@@ -1,4 +1,5 @@
1
i386_system_ss.add(when: 'CONFIG_WHPX', if_true: files(
2
'whpx-all.c',
3
'whpx-apic.c',
4
+ 'whpx-cpu-legacy.c'
5
))
target/i386/whpx/whpx-all.c
+160
-5
@@ -36,6 +36,7 @@
36
#include "system/whpx-accel-ops.h"
37
#include "system/whpx-all.h"
38
#include "system/whpx-common.h"
39
+#include "whpx-i386.h"
40
41
#include "emulate/x86_decode.h"
42
#include "emulate/x86_emu.h"
@@ -49,6 +50,8 @@
50
/* for kernel-irqchip=off */
51
#define HV_X64_MSR_APIC_FREQUENCY 0x40000023
52
53
+static bool is_modern_os = true;
54
+
55
static const WHV_REGISTER_NAME whpx_register_names[] = {
56
57
/* X64 General purpose registers */
@@ -265,11 +268,30 @@ typedef enum WhpxStepMode {
268
static uint32_t max_vcpu_index;
269
static WHV_PROCESSOR_XSAVE_FEATURES whpx_xsave_cap;
270
268
-static bool whpx_has_xsave(void)
271
+bool whpx_has_xsave(void)
272
{
273
return whpx_xsave_cap.XsaveSupport;
274
}
275
276
+bool whpx_has_xsaves(void)
277
+{
278
+ return whpx_xsave_cap.XsaveSupervisorSupport;
279
+}
280
+
281
+static bool whpx_rdtsc_cap;
282
+
283
+bool whpx_has_rdtscp(void)
284
+{
285
+ return whpx_rdtsc_cap;
286
+}
287
+
288
+static bool whpx_invpcid_cap;
289
+
290
+bool whpx_has_invpcid(void)
291
+{
292
+ return whpx_invpcid_cap;
293
+}
294
+
295
static WHV_X64_SEGMENT_REGISTER whpx_seg_q2h(const SegmentCache *qs, int v86,
296
int r86)
297
{
@@ -1062,6 +1084,137 @@ static void whpx_init_emu(void)
1084
init_emu(&whpx_x86_emul_ops);
1085
}
1086
1087
+bool whpx_is_legacy_os(void)
1088
+{
1089
+ return !is_modern_os;
1090
+}
1091
+
1092
+uint32_t whpx_get_supported_cpuid(uint32_t func, uint32_t idx, int reg)
1093
+{
1094
+ WHV_CPUID_OUTPUT output = {};
1095
+ uint32_t eax, ebx, ecx, edx;
1096
+ uint32_t cpu_index = 0;
1097
+ bool temp_cpu = true;
1098
+ HRESULT hr;
1099
+
1100
+ /* Legacy OSes don't have WHvGetVirtualProcessorCpuidOutput */
1101
+ if (whpx_is_legacy_os()) {
1102
+ return whpx_get_supported_cpuid_legacy(func, idx, reg);
1103
+ }
1104
+
1105
+ hr = whp_dispatch.WHvCreateVirtualProcessor(
1106
+ whpx_global.partition, cpu_index, 0);
1107
+
1108
+ /* This means that the CPU already exists... */
1109
+ if (FAILED(hr)) {
1110
+ temp_cpu = false;
1111
+ }
1112
+
1113
+ hr = whp_dispatch.WHvGetVirtualProcessorCpuidOutput(whpx_global.partition,
1114
+ cpu_index, func, idx, &output);
1115
+
1116
+ if (FAILED(hr)) {
1117
+ abort();
1118
+ }
1119
+
1120
+ if (temp_cpu) {
1121
+ hr = whp_dispatch.WHvDeleteVirtualProcessor(whpx_global.partition, cpu_index);
1122
+ if (FAILED(hr)) {
1123
+ abort();
1124
+ }
1125
+ }
1126
+
1127
+ eax = output.Eax;
1128
+ ebx = output.Ebx;
1129
+ ecx = output.Ecx;
1130
+ edx = output.Edx;
1131
+
1132
+ /*
1133
+ * We can emulate X2APIC even for the kernel-irqchip=off case.
1134
+ * CPUID_EXT_HYPERVISOR and CPUID_HT should be considered present
1135
+ * always, so report them as unconditionally supported here.
1136
+ */
1137
+ if (func == 1) {
1138
+ ecx |= CPUID_EXT_X2APIC;
1139
+ ecx |= CPUID_EXT_HYPERVISOR;
1140
+ edx |= CPUID_HT;
1141
+ }
1142
+
1143
+ switch (reg) {
1144
+ case R_EAX:
1145
+ return eax;
1146
+ case R_EBX:
1147
+ return ebx;
1148
+ case R_ECX:
1149
+ return ecx;
1150
+ case R_EDX:
1151
+ return edx;
1152
+ default:
1153
+ return 0;
1154
+ }
1155
+}
1156
+
1157
+uint64_t whpx_get_supported_msr_feature(uint32_t index)
1158
+{
1159
+ WHV_CAPABILITY_CODE cap;
1160
+ uint64_t val = 0;
1161
+
1162
+ switch (index) {
1163
+ case MSR_IA32_VMX_BASIC:
1164
+ cap = WHvCapabilityCodeVmxBasic;
1165
+ break;
1166
+ case MSR_IA32_VMX_MISC:
1167
+ cap = WHvCapabilityCodeVmxMisc;
1168
+ break;
1169
+ case MSR_IA32_VMX_CR0_FIXED0:
1170
+ cap = WHvCapabilityCodeVmxCr0Fixed0;
1171
+ break;
1172
+ case MSR_IA32_VMX_CR0_FIXED1:
1173
+ cap = WHvCapabilityCodeVmxCr0Fixed1;
1174
+ break;
1175
+ case MSR_IA32_VMX_CR4_FIXED0:
1176
+ cap = WHvCapabilityCodeVmxCr4Fixed0;
1177
+ break;
1178
+ case MSR_IA32_VMX_CR4_FIXED1:
1179
+ cap = WHvCapabilityCodeVmxCr4Fixed1;
1180
+ break;
1181
+ case MSR_IA32_VMX_VMCS_ENUM:
1182
+ cap = WHvCapabilityCodeVmxVmcsEnum;
1183
+ break;
1184
+ case MSR_IA32_VMX_PROCBASED_CTLS2:
1185
+ cap = WHvCapabilityCodeVmxProcbasedCtls2;
1186
+ break;
1187
+ case MSR_IA32_VMX_EPT_VPID_CAP:
1188
+ cap = WHvCapabilityCodeVmxEptVpidCap;
1189
+ break;
1190
+ case MSR_IA32_VMX_TRUE_PINBASED_CTLS:
1191
+ cap = WHvCapabilityCodeVmxPinbasedCtls;
1192
+ break;
1193
+ case MSR_IA32_VMX_TRUE_PROCBASED_CTLS:
1194
+ cap = WHvCapabilityCodeVmxProcbasedCtls;
1195
+ break;
1196
+ case MSR_IA32_VMX_TRUE_ENTRY_CTLS:
1197
+ cap = WHvCapabilityCodeVmxTrueEntryCtls;
1198
+ break;
1199
+ case MSR_IA32_VMX_TRUE_EXIT_CTLS:
1200
+ cap = WHvCapabilityCodeVmxTrueExitCtls;
1201
+ break;
1202
+ default:
1203
+ cap = 0;
1204
+ }
1205
+
1206
+ if (cap != 0) {
1207
+ HRESULT hr = whp_dispatch.WHvGetCapability(
1208
+ cap, &val, sizeof(val),
1209
+ NULL);
1210
+ if (FAILED(hr)) {
1211
+ return 0;
1212
+ }
1213
+ return val;
1214
+ }
1215
+ return 0;
1216
+}
1217
+
1218
/*
1219
* Controls whether we should intercept various exceptions on the guest,
1220
* namely breakpoint/single-step events.
@@ -2235,7 +2388,6 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2388
WHV_CAPABILITY_FEATURES features = {0};
2389
WHV_PROCESSOR_FEATURES_BANKS processor_features;
2390
WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
2238
- bool is_legacy_os = false;
2391
UINT32 cpuidExitList[] = {1};
2392
2393
whpx = &whpx_global;
@@ -2355,6 +2507,9 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2507
goto error;
2508
}
2509
2510
+ whpx_rdtsc_cap = processor_features.Bank0.RdtscpSupport;
2511
+ whpx_invpcid_cap = processor_features.Bank0.InvpcidSupport;
2512
+
2513
if (whpx_irqchip_in_kernel() && processor_features.Bank1.NestedVirtSupport) {
2514
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
2515
prop.NestedVirtualization = 1;
@@ -2395,7 +2550,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2550
if (FAILED(hr)) {
2551
warn_report("WHPX: Failed to get performance "
2552
"monitoring features, hr=%08lx", hr);
2398
- is_legacy_os = true;
2553
+ is_modern_os = false;
2554
} else {
2555
hr = whp_dispatch.WHvSetPartitionProperty(
2556
whpx->partition,
@@ -2435,7 +2590,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2590
synthetic_features.Bank0.DirectSyntheticTimers = 1;
2591
}
2592
2438
- if (!is_legacy_os && whpx->hyperv_enlightenments_allowed) {
2593
+ if (is_modern_os && whpx->hyperv_enlightenments_allowed) {
2594
hr = whp_dispatch.WHvSetPartitionProperty(
2595
whpx->partition,
2596
WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks,
@@ -2446,7 +2601,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2601
ret = -EINVAL;
2602
goto error;
2603
}
2449
- } else if (is_legacy_os && whpx->hyperv_enlightenments_required) {
2604
+ } else if (!is_modern_os && whpx->hyperv_enlightenments_required) {
2605
error_report("Hyper-V enlightenments not available on legacy Windows");
2606
ret = -EINVAL;
2607
goto error;
target/i386/whpx/whpx-cpu-legacy.c
new
+171
@@ -0,0 +1,171 @@
1
+/*
2
+ * i386 CPUID helper functions
3
+ *
4
+ * Copyright (c) 2003 Fabrice Bellard
5
+ * Copyright (c) 2017 Google Inc.
6
+ *
7
+ * This program is free software; you can redistribute it and/or
8
+ * modify it under the terms of the GNU Lesser General Public
9
+ * License as published by the Free Software Foundation; either
10
+ * version 2.1 of the License, or (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ * Lesser General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Lesser General Public
18
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
19
+ *
20
+ * cpuid
21
+ */
22
+
23
+#include "qemu/osdep.h"
24
+#include "qemu/cpuid.h"
25
+#include "host/cpuinfo.h"
26
+#include "cpu.h"
27
+#include "emulate/x86.h"
28
+#include "whpx-i386.h"
29
+
30
+static bool cached_xcr0;
31
+static uint64_t supported_xcr0;
32
+
33
+static void cache_host_xcr0(void)
34
+{
35
+ if (cached_xcr0) {
36
+ return;
37
+ }
38
+
39
+ if (whpx_has_xsave()) {
40
+ uint64_t host_xcr0 = xgetbv_low(0);
41
+
42
+ /* Only show xcr0 bits corresponding to usable features. */
43
+ supported_xcr0 = host_xcr0 & (XSTATE_FP_MASK |
44
+ XSTATE_SSE_MASK | XSTATE_YMM_MASK |
45
+ XSTATE_OPMASK_MASK | XSTATE_ZMM_Hi256_MASK |
46
+ XSTATE_Hi16_ZMM_MASK);
47
+ if ((supported_xcr0 & (XSTATE_FP_MASK | XSTATE_SSE_MASK)) !=
48
+ (XSTATE_FP_MASK | XSTATE_SSE_MASK)) {
49
+ supported_xcr0 = 0;
50
+ }
51
+ }
52
+
53
+ cached_xcr0 = true;
54
+}
55
+
56
+uint32_t whpx_get_supported_cpuid_legacy(uint32_t func, uint32_t idx,
57
+ int reg)
58
+{
59
+ uint32_t eax, ebx, ecx, edx;
60
+
61
+ cache_host_xcr0();
62
+ host_cpuid(func, idx, &eax, &ebx, &ecx, &edx);
63
+
64
+ switch (func) {
65
+ case 0:
66
+ eax = eax < (uint32_t)0xd ? eax : (uint32_t)0xd;
67
+ break;
68
+ case 1:
69
+ edx &= CPUID_FP87 | CPUID_VME | CPUID_DE | CPUID_PSE | CPUID_TSC |
70
+ CPUID_MSR | CPUID_PAE | CPUID_MCE | CPUID_CX8 | CPUID_APIC |
71
+ CPUID_SEP | CPUID_MTRR | CPUID_PGE | CPUID_MCA | CPUID_CMOV |
72
+ CPUID_PAT | CPUID_PSE36 | CPUID_CLFLUSH | CPUID_MMX |
73
+ CPUID_FXSR | CPUID_SSE | CPUID_SSE2 | CPUID_SS | CPUID_HT;
74
+ ecx &= CPUID_EXT_SSE3 | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSSE3 |
75
+ CPUID_EXT_FMA | CPUID_EXT_CX16 | CPUID_EXT_PCID |
76
+ CPUID_EXT_SSE41 | CPUID_EXT_SSE42 | CPUID_EXT_MOVBE |
77
+ CPUID_EXT_POPCNT | CPUID_EXT_AES |
78
+ (supported_xcr0 ? CPUID_EXT_XSAVE : 0) |
79
+ CPUID_EXT_AVX | CPUID_EXT_F16C | CPUID_EXT_RDRAND;
80
+ ecx |= CPUID_EXT_HYPERVISOR;
81
+ ecx |= CPUID_EXT_X2APIC;
82
+ edx |= CPUID_HT;
83
+ break;
84
+ case 6:
85
+ eax = CPUID_6_EAX_ARAT;
86
+ ebx = 0;
87
+ ecx = 0;
88
+ edx = 0;
89
+ break;
90
+ case 7:
91
+ if (idx == 0) {
92
+ ebx &= CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
93
+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 |
94
+ CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_BMI2 |
95
+ CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_RTM |
96
+ CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
97
+ CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_AVX512IFMA |
98
+ CPUID_7_0_EBX_AVX512F | CPUID_7_0_EBX_AVX512PF |
99
+ CPUID_7_0_EBX_AVX512ER | CPUID_7_0_EBX_AVX512CD |
100
+ CPUID_7_0_EBX_CLFLUSHOPT | CPUID_7_0_EBX_CLWB |
101
+ CPUID_7_0_EBX_AVX512DQ | CPUID_7_0_EBX_SHA_NI |
102
+ CPUID_7_0_EBX_AVX512BW | CPUID_7_0_EBX_AVX512VL |
103
+ CPUID_7_0_EBX_INVPCID;
104
+
105
+ if (!whpx_has_invpcid()) {
106
+ ebx &= ~CPUID_7_0_EBX_INVPCID;
107
+ }
108
+
109
+ ecx &= CPUID_7_0_ECX_AVX512_VBMI | CPUID_7_0_ECX_AVX512_VPOPCNTDQ |
110
+ CPUID_7_0_ECX_RDPID;
111
+ edx &= CPUID_7_0_EDX_AVX512_4VNNIW | CPUID_7_0_EDX_AVX512_4FMAPS;
112
+ } else {
113
+ ebx = 0;
114
+ ecx = 0;
115
+ edx = 0;
116
+ }
117
+ eax = 0;
118
+ break;
119
+ case 0xD:
120
+ if (!supported_xcr0 || idx >= 63 ||
121
+ (idx > 1 && !(supported_xcr0 & (UINT64_C(1) << idx)))) {
122
+ eax = ebx = ecx = edx = 0;
123
+ break;
124
+ }
125
+
126
+ if (idx == 0) {
127
+ eax = supported_xcr0;
128
+ } else if (idx == 1) {
129
+ eax &= CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XGETBV1;
130
+ if (!whpx_has_xsaves()) {
131
+ eax &= ~CPUID_XSAVE_XSAVES;
132
+ }
133
+ }
134
+ break;
135
+ case 0x80000001:
136
+ /* LM only if HVF in 64-bit mode */
137
+ edx &= CPUID_FP87 | CPUID_VME | CPUID_DE | CPUID_PSE | CPUID_TSC |
138
+ CPUID_MSR | CPUID_PAE | CPUID_MCE | CPUID_CX8 | CPUID_APIC |
139
+ CPUID_EXT2_SYSCALL | CPUID_MTRR | CPUID_PGE | CPUID_MCA | CPUID_CMOV |
140
+ CPUID_PAT | CPUID_PSE36 | CPUID_EXT2_MMXEXT | CPUID_MMX |
141
+ CPUID_FXSR | CPUID_EXT2_FXSR | CPUID_EXT2_PDPE1GB | CPUID_EXT2_3DNOWEXT |
142
+ CPUID_EXT2_3DNOW | CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX;
143
+ if (!(whpx_has_rdtscp())) {
144
+ edx &= ~CPUID_EXT2_RDTSCP;
145
+ }
146
+ ecx &= CPUID_EXT3_LAHF_LM | CPUID_EXT3_CMP_LEG | CPUID_EXT3_CR8LEG |
147
+ CPUID_EXT3_ABM | CPUID_EXT3_SSE4A | CPUID_EXT3_MISALIGNSSE |
148
+ CPUID_EXT3_3DNOWPREFETCH | CPUID_EXT3_OSVW | CPUID_EXT3_XOP |
149
+ CPUID_EXT3_FMA4 | CPUID_EXT3_TBM;
150
+ break;
151
+ case 0x80000007:
152
+ edx &= CPUID_APM_INVTSC;
153
+ eax = ebx = ecx = 0;
154
+ break;
155
+ default:
156
+ return 0;
157
+ }
158
+
159
+ switch (reg) {
160
+ case R_EAX:
161
+ return eax;
162
+ case R_EBX:
163
+ return ebx;
164
+ case R_ECX:
165
+ return ecx;
166
+ case R_EDX:
167
+ return edx;
168
+ default:
169
+ return 0;
170
+ }
171
+}
target/i386/whpx/whpx-i386.h
new
+12
@@ -0,0 +1,12 @@
1
+/* SPDX-License-Identifier: GPL-2.0-or-later */
2
+
3
+uint32_t whpx_get_supported_cpuid(uint32_t func, uint32_t idx, int reg);
4
+uint64_t whpx_get_supported_msr_feature(uint32_t index);
5
+bool whpx_is_legacy_os(void);
6
+
7
+uint32_t whpx_get_supported_cpuid_legacy(uint32_t func, uint32_t idx,
8
+ int reg);
9
+bool whpx_has_xsave(void);
10
+bool whpx_has_xsaves(void);
11
+bool whpx_has_rdtscp(void);
12
+bool whpx_has_invpcid(void);