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>
This commit is contained in:
Mohamed Mediouni
2026-04-22 23:42:03 +02:00
committed by Paolo Bonzini
parent 72bef33b15
commit 478e27cf87
3 changed files with 84 additions and 22 deletions

View File

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

View File

@@ -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,

View File

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