Commit 478e27cf87 for qemu.org
commit 478e27cf879ec2d12deb358d1c7fddb7935fef30
Author: Mohamed Mediouni <mohamed@unpredictable.fr>
Date: Wed Apr 22 23:42:03 2026 +0200
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>
diff --git a/include/system/whpx-common.h b/include/system/whpx-common.h
index 3406c20fec..79710e2fb3 100644
--- a/include/system/whpx-common.h
+++ b/include/system/whpx-common.h
@@ -8,7 +8,6 @@ struct AccelCPUState {
bool interruptable;
bool ready_for_pic_interrupt;
uint64_t tpr;
- uint64_t apic_base;
bool interruption_pending;
/* Must be the last field as it may have a tail */
WHV_RUN_VP_EXIT_CONTEXT exit_ctx;
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 012fa6d021..b055644580 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -139,7 +139,6 @@ static const WHV_REGISTER_NAME whpx_register_names[] = {
#ifdef TARGET_X86_64
WHvX64RegisterKernelGsBase,
#endif
- WHvX64RegisterApicBase,
/* WHvX64RegisterPat, */
WHvX64RegisterSysenterCs,
WHvX64RegisterSysenterEip,
@@ -420,7 +419,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
r86 = !(env->cr[0] & CR0_PE_MASK);
vcpu->tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
- vcpu->apic_base = cpu_get_apic_base(x86_cpu->apic_state);
idx = 0;
@@ -538,9 +536,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
vcxt.values[idx++].Reg64 = env->kernelgsbase;
#endif
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
- vcxt.values[idx++].Reg64 = vcpu->apic_base;
-
/* WHvX64RegisterPat - Skipped */
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -575,6 +570,12 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
error_report("WHPX: Failed to set virtual processor context, hr=%08lx",
hr);
}
+
+ if (level >= WHPX_LEVEL_FULL_STATE) {
+ WHV_REGISTER_VALUE apic_base = {};
+ apic_base.Reg64 = cpu_get_apic_base(X86_CPU(cpu)->apic_state);
+ whpx_set_reg(cpu, WHvX64RegisterApicBase, apic_base);
+ }
}
static int whpx_get_tsc(CPUState *cpu)
@@ -666,7 +667,7 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
X86CPU *x86_cpu = X86_CPU(cpu);
CPUX86State *env = &x86_cpu->env;
struct whpx_register_set vcxt;
- uint64_t tpr, apic_base;
+ uint64_t tpr;
HRESULT hr;
int idx;
int idx_next;
@@ -798,13 +799,6 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
env->kernelgsbase = vcxt.values[idx++].Reg64;
#endif
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
- apic_base = vcxt.values[idx++].Reg64;
- if (apic_base != vcpu->apic_base) {
- vcpu->apic_base = apic_base;
- cpu_set_apic_base(x86_cpu->apic_state, vcpu->apic_base);
- }
-
/* WHvX64RegisterPat - Skipped */
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -2082,8 +2076,7 @@ int whpx_vcpu_run(CPUState *cpu)
val = X86_CPU(cpu)->env.apic_bus_freq;
}
- if (!whpx_irqchip_in_kernel() &&
- vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
+ if (vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
is_known_msr = 1;
if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
/* Read path unreachable on Hyper-V */
@@ -2233,6 +2226,13 @@ int whpx_vcpu_run(CPUState *cpu)
} else {
reg_values[2].Reg32 &= ~CPUID_EXT_X2APIC;
}
+
+ /* CPUID[1:EDX].APIC is dynamic */
+ if (env->features[FEAT_1_EDX] & CPUID_APIC) {
+ reg_values[3].Reg32 |= CPUID_APIC;
+ } else {
+ reg_values[3].Reg32 &= ~CPUID_APIC;
+ }
}
/* Dynamic depending on XCR0 and XSS, so query DefaultResult */
@@ -2804,9 +2804,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.X64MsrExitBitmap.UnhandledMsrs = 1;
- if (!whpx_irqchip_in_kernel()) {
- prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
- }
+ prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
diff --git a/target/i386/whpx/whpx-apic.c b/target/i386/whpx/whpx-apic.c
index f26ecaf6e8..65629ca45f 100644
--- a/target/i386/whpx/whpx-apic.c
+++ b/target/i386/whpx/whpx-apic.c
@@ -90,9 +90,70 @@ static void whpx_get_apic_state(APICCommonState *s,
apic_next_timer(s, s->initial_count_load_time);
}
-static int whpx_apic_set_base(APICCommonState *s, uint64_t val)
+static int apic_set_base_check(APICCommonState *s, uint64_t val)
{
- s->apicbase = val;
+ /* Enable x2apic when x2apic is not supported by CPU */
+ if (!cpu_has_x2apic_feature(&s->cpu->env) &&
+ val & MSR_IA32_APICBASE_EXTD) {
+ return -1;
+ }
+
+ /*
+ * Transition into invalid state
+ * (s->apicbase & MSR_IA32_APICBASE_ENABLE == 0) &&
+ * (s->apicbase & MSR_IA32_APICBASE_EXTD) == 1
+ */
+ if (!(val & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ /* Invalid transition from disabled mode to x2APIC */
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ /* Invalid transition from x2APIC to xAPIC */
+ if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ (s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_ENABLE) &&
+ !(val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int apic_set_base(APICCommonState *s, uint64_t val)
+{
+ if (apic_set_base_check(s, val) < 0) {
+ return -1;
+ }
+
+ s->apicbase = (val & MSR_IA32_APICBASE_BASE) |
+ (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE));
+ if (!(val & MSR_IA32_APICBASE_ENABLE)) {
+ s->apicbase &= ~MSR_IA32_APICBASE_ENABLE;
+ cpu_clear_apic_feature(&s->cpu->env);
+ }
+
+ /* Transition from disabled mode to xAPIC */
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_ENABLE)) {
+ s->apicbase |= MSR_IA32_APICBASE_ENABLE;
+ cpu_set_apic_feature(&s->cpu->env);
+ }
+
+ /* Transition from xAPIC to x2APIC */
+ if (cpu_has_x2apic_feature(&s->cpu->env) &&
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ s->apicbase |= MSR_IA32_APICBASE_EXTD;
+ }
+
return 0;
}
@@ -235,6 +296,10 @@ static void whpx_apic_mem_write(void *opaque, hwaddr addr,
static const MemoryRegionOps whpx_apic_io_ops = {
.read = whpx_apic_mem_read,
.write = whpx_apic_mem_write,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};
@@ -262,7 +327,7 @@ static void whpx_apic_class_init(ObjectClass *klass, const void *data)
k->realize = whpx_apic_realize;
k->reset = whpx_apic_reset;
- k->set_base = whpx_apic_set_base;
+ k->set_base = apic_set_base;
k->set_tpr = whpx_apic_set_tpr;
k->get_tpr = whpx_apic_get_tpr;
k->post_load = whpx_apic_post_load;