whpx: i386: fix CPUID[1:EDX].APIC reporting
Hyper-V always has CPUID[1:EDX].APIC set, even when the APIC isn't enabled yet. Work around this by also using the APICBASE trap for kernel-irqchip=on. Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr> Link: https://lore.kernel.org/r/20260422214225.2242-16-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Mohamed Mediouni committed
Apr 22, 2026 at 23:42 UTC
478e27cf879ec2d12deb358d1c7fddb7935fef30
3 files changed
+84
-22
include/system/whpx-common.h
-1
@@ -8,7 +8,6 @@ struct AccelCPUState {
8
bool interruptable;
9
bool ready_for_pic_interrupt;
10
uint64_t tpr;
11
- uint64_t apic_base;
11
bool interruption_pending;
12
/* Must be the last field as it may have a tail */
13
WHV_RUN_VP_EXIT_CONTEXT exit_ctx;
target/i386/whpx/whpx-all.c
+16
-18
@@ -139,7 +139,6 @@ static const WHV_REGISTER_NAME whpx_register_names[] = {
139
#ifdef TARGET_X86_64
140
WHvX64RegisterKernelGsBase,
141
#endif
142
- WHvX64RegisterApicBase,
142
/* WHvX64RegisterPat, */
143
WHvX64RegisterSysenterCs,
144
WHvX64RegisterSysenterEip,
@@ -420,7 +419,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
419
r86 = !(env->cr[0] & CR0_PE_MASK);
420
421
vcpu->tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
423
- vcpu->apic_base = cpu_get_apic_base(x86_cpu->apic_state);
422
423
idx = 0;
424
@@ -538,9 +536,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
536
vcxt.values[idx++].Reg64 = env->kernelgsbase;
537
#endif
538
541
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
542
- vcxt.values[idx++].Reg64 = vcpu->apic_base;
543
-
539
/* WHvX64RegisterPat - Skipped */
540
541
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -575,6 +570,12 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
570
error_report("WHPX: Failed to set virtual processor context, hr=%08lx",
571
hr);
572
}
573
+
574
+ if (level >= WHPX_LEVEL_FULL_STATE) {
575
+ WHV_REGISTER_VALUE apic_base = {};
576
+ apic_base.Reg64 = cpu_get_apic_base(X86_CPU(cpu)->apic_state);
577
+ whpx_set_reg(cpu, WHvX64RegisterApicBase, apic_base);
578
+ }
579
}
580
581
static int whpx_get_tsc(CPUState *cpu)
@@ -666,7 +667,7 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
667
X86CPU *x86_cpu = X86_CPU(cpu);
668
CPUX86State *env = &x86_cpu->env;
669
struct whpx_register_set vcxt;
669
- uint64_t tpr, apic_base;
670
+ uint64_t tpr;
671
HRESULT hr;
672
int idx;
673
int idx_next;
@@ -798,13 +799,6 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
799
env->kernelgsbase = vcxt.values[idx++].Reg64;
800
#endif
801
801
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
802
- apic_base = vcxt.values[idx++].Reg64;
803
- if (apic_base != vcpu->apic_base) {
804
- vcpu->apic_base = apic_base;
805
- cpu_set_apic_base(x86_cpu->apic_state, vcpu->apic_base);
806
- }
807
-
802
/* WHvX64RegisterPat - Skipped */
803
804
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -2082,8 +2076,7 @@ int whpx_vcpu_run(CPUState *cpu)
2076
val = X86_CPU(cpu)->env.apic_bus_freq;
2077
}
2078
2085
- if (!whpx_irqchip_in_kernel() &&
2086
- vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
2079
+ if (vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
2080
is_known_msr = 1;
2081
if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
2082
/* Read path unreachable on Hyper-V */
@@ -2233,6 +2226,13 @@ int whpx_vcpu_run(CPUState *cpu)
2226
} else {
2227
reg_values[2].Reg32 &= ~CPUID_EXT_X2APIC;
2228
}
2229
+
2230
+ /* CPUID[1:EDX].APIC is dynamic */
2231
+ if (env->features[FEAT_1_EDX] & CPUID_APIC) {
2232
+ reg_values[3].Reg32 |= CPUID_APIC;
2233
+ } else {
2234
+ reg_values[3].Reg32 &= ~CPUID_APIC;
2235
+ }
2236
}
2237
2238
/* Dynamic depending on XCR0 and XSS, so query DefaultResult */
@@ -2804,9 +2804,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
2804
2805
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
2806
prop.X64MsrExitBitmap.UnhandledMsrs = 1;
2807
- if (!whpx_irqchip_in_kernel()) {
2808
- prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
2809
- }
2807
+ prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
2808
2809
hr = whp_dispatch.WHvSetPartitionProperty(
2810
whpx->partition,
target/i386/whpx/whpx-apic.c
+68
-3
@@ -90,9 +90,70 @@ static void whpx_get_apic_state(APICCommonState *s,
90
apic_next_timer(s, s->initial_count_load_time);
91
}
92
93
-static int whpx_apic_set_base(APICCommonState *s, uint64_t val)
93
+static int apic_set_base_check(APICCommonState *s, uint64_t val)
94
{
95
- s->apicbase = val;
95
+ /* Enable x2apic when x2apic is not supported by CPU */
96
+ if (!cpu_has_x2apic_feature(&s->cpu->env) &&
97
+ val & MSR_IA32_APICBASE_EXTD) {
98
+ return -1;
99
+ }
100
+
101
+ /*
102
+ * Transition into invalid state
103
+ * (s->apicbase & MSR_IA32_APICBASE_ENABLE == 0) &&
104
+ * (s->apicbase & MSR_IA32_APICBASE_EXTD) == 1
105
+ */
106
+ if (!(val & MSR_IA32_APICBASE_ENABLE) &&
107
+ (val & MSR_IA32_APICBASE_EXTD)) {
108
+ return -1;
109
+ }
110
+
111
+ /* Invalid transition from disabled mode to x2APIC */
112
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
113
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
114
+ (val & MSR_IA32_APICBASE_ENABLE) &&
115
+ (val & MSR_IA32_APICBASE_EXTD)) {
116
+ return -1;
117
+ }
118
+
119
+ /* Invalid transition from x2APIC to xAPIC */
120
+ if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
121
+ (s->apicbase & MSR_IA32_APICBASE_EXTD) &&
122
+ (val & MSR_IA32_APICBASE_ENABLE) &&
123
+ !(val & MSR_IA32_APICBASE_EXTD)) {
124
+ return -1;
125
+ }
126
+
127
+ return 0;
128
+}
129
+
130
+static int apic_set_base(APICCommonState *s, uint64_t val)
131
+{
132
+ if (apic_set_base_check(s, val) < 0) {
133
+ return -1;
134
+ }
135
+
136
+ s->apicbase = (val & MSR_IA32_APICBASE_BASE) |
137
+ (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE));
138
+ if (!(val & MSR_IA32_APICBASE_ENABLE)) {
139
+ s->apicbase &= ~MSR_IA32_APICBASE_ENABLE;
140
+ cpu_clear_apic_feature(&s->cpu->env);
141
+ }
142
+
143
+ /* Transition from disabled mode to xAPIC */
144
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
145
+ (val & MSR_IA32_APICBASE_ENABLE)) {
146
+ s->apicbase |= MSR_IA32_APICBASE_ENABLE;
147
+ cpu_set_apic_feature(&s->cpu->env);
148
+ }
149
+
150
+ /* Transition from xAPIC to x2APIC */
151
+ if (cpu_has_x2apic_feature(&s->cpu->env) &&
152
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
153
+ (val & MSR_IA32_APICBASE_EXTD)) {
154
+ s->apicbase |= MSR_IA32_APICBASE_EXTD;
155
+ }
156
+
157
return 0;
158
}
159
@@ -235,6 +296,10 @@ static void whpx_apic_mem_write(void *opaque, hwaddr addr,
296
static const MemoryRegionOps whpx_apic_io_ops = {
297
.read = whpx_apic_mem_read,
298
.write = whpx_apic_mem_write,
299
+ .impl.min_access_size = 1,
300
+ .impl.max_access_size = 4,
301
+ .valid.min_access_size = 1,
302
+ .valid.max_access_size = 4,
303
.endianness = DEVICE_LITTLE_ENDIAN,
304
};
305
@@ -262,7 +327,7 @@ static void whpx_apic_class_init(ObjectClass *klass, const void *data)
327
328
k->realize = whpx_apic_realize;
329
k->reset = whpx_apic_reset;
265
- k->set_base = whpx_apic_set_base;
330
+ k->set_base = apic_set_base;
331
k->set_tpr = whpx_apic_set_tpr;
332
k->get_tpr = whpx_apic_get_tpr;
333
k->post_load = whpx_apic_post_load;