@samitouri / QOSamiQemu / commits / 057143ba75

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);