Change terminology to match the KVM one, as APIC is x86-specific. And move out whpx_irqchip_in_kernel() to make it usable from common code even when not compiling with WHPX support.
Signed-off-by: Mohamed Mediouni <[email protected]> Reviewed-by: Pierrick Bouvier <[email protected]> Reviewed-by: Akihiko Odaki <[email protected]> --- MAINTAINERS | 1 + accel/stubs/whpx-stub.c | 1 + accel/whpx/whpx-accel-ops.c | 2 +- accel/whpx/whpx-common.c | 10 +- hw/arm/virt.c | 9 ++ hw/i386/x86-cpu.c | 4 +- hw/intc/arm_gicv3_common.c | 3 + hw/intc/arm_gicv3_whpx.c | 237 +++++++++++++++++++++++++++++ hw/intc/meson.build | 1 + include/hw/intc/arm_gicv3_common.h | 3 + include/system/whpx-internal.h | 1 - include/system/whpx.h | 5 +- target/i386/cpu-apic.c | 2 +- target/i386/whpx/whpx-all.c | 14 +- 14 files changed, 270 insertions(+), 23 deletions(-) create mode 100644 hw/intc/arm_gicv3_whpx.c diff --git a/MAINTAINERS b/MAINTAINERS index 873d6628cd..126f72e587 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -566,6 +566,7 @@ M: Mohamed Mediouni <[email protected]> S: Supported F: accel/whpx/ F: target/i386/whpx/ +F: hw/intc/arm_gicv3_whpx.c F: accel/stubs/whpx-stub.c F: include/system/whpx.h F: include/system/whpx-accel-ops.h diff --git a/accel/stubs/whpx-stub.c b/accel/stubs/whpx-stub.c index c564c89fd0..4529dc4f78 100644 --- a/accel/stubs/whpx-stub.c +++ b/accel/stubs/whpx-stub.c @@ -10,3 +10,4 @@ #include "system/whpx.h" bool whpx_allowed; +bool whpx_irqchip_in_kernel; diff --git a/accel/whpx/whpx-accel-ops.c b/accel/whpx/whpx-accel-ops.c index c84a25c273..50fadea0fd 100644 --- a/accel/whpx/whpx-accel-ops.c +++ b/accel/whpx/whpx-accel-ops.c @@ -78,7 +78,7 @@ static void whpx_kick_vcpu_thread(CPUState *cpu) static bool whpx_vcpu_thread_is_idle(CPUState *cpu) { - return !whpx_apic_in_platform(); + return !whpx_irqchip_in_kernel(); } static void whpx_accel_ops_class_init(ObjectClass *oc, const void *data) diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c index c0610815d9..05f9e520b7 100644 --- a/accel/whpx/whpx-common.c +++ b/accel/whpx/whpx-common.c @@ -35,6 +35,7 @@ #include <winhvplatformdefs.h> bool whpx_allowed; +bool whpx_irqchip_in_kernel; static bool whp_dispatch_initialized; static HMODULE hWinHvPlatform; #ifdef HOST_X86_64 @@ -488,15 +489,6 @@ static const TypeInfo whpx_cpu_accel_type = { .abstract = true, }; -/* - * Partition support - */ - -bool whpx_apic_in_platform(void) -{ - return whpx_global.apic_in_platform; -} - static void whpx_accel_class_init(ObjectClass *oc, const void *data) { AccelClass *ac = ACCEL_CLASS(oc); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index aa5e992712..b7eb0cec5e 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -49,6 +49,7 @@ #include "system/tcg.h" #include "system/kvm.h" #include "system/hvf.h" +#include "system/whpx.h" #include "system/qtest.h" #include "system/system.h" #include "hw/core/loader.h" @@ -2114,6 +2115,8 @@ static void finalize_gic_version(VirtMachineState *vms) /* KVM w/o kernel irqchip can only deal with GICv2 */ gics_supported |= VIRT_GIC_VERSION_2_MASK; accel_name = "KVM with kernel-irqchip=off"; + } else if (whpx_enabled()) { + gics_supported |= VIRT_GIC_VERSION_3_MASK; } else if (tcg_enabled() || hvf_enabled() || qtest_enabled()) { gics_supported |= VIRT_GIC_VERSION_2_MASK; if (module_object_class_by_name("arm-gicv3")) { @@ -2154,6 +2157,8 @@ static void finalize_msi_controller(VirtMachineState *vms) if (vms->msi_controller == VIRT_MSI_CTRL_AUTO) { if (vms->gic_version == VIRT_GIC_VERSION_2) { vms->msi_controller = VIRT_MSI_CTRL_GICV2M; + } else if (whpx_enabled()) { + vms->msi_controller = VIRT_MSI_CTRL_GICV2M; } else { vms->msi_controller = VIRT_MSI_CTRL_ITS; } @@ -2169,6 +2174,10 @@ static void finalize_msi_controller(VirtMachineState *vms) error_report("GICv2 + ITS is an invalid configuration."); exit(1); } + if (whpx_enabled()) { + error_report("ITS not supported on WHPX."); + exit(1); + } } assert(vms->msi_controller != VIRT_MSI_CTRL_AUTO); diff --git a/hw/i386/x86-cpu.c b/hw/i386/x86-cpu.c index 276f2b0cdf..95e08e3c2a 100644 --- a/hw/i386/x86-cpu.c +++ b/hw/i386/x86-cpu.c @@ -45,7 +45,7 @@ static void pic_irq_request(void *opaque, int irq, int level) trace_x86_pic_interrupt(irq, level); if (cpu_is_apic_enabled(cpu->apic_state) && !kvm_irqchip_in_kernel() && - !whpx_apic_in_platform()) { + !whpx_irqchip_in_kernel()) { CPU_FOREACH(cs) { cpu = X86_CPU(cs); if (apic_accept_pic_intr(cpu->apic_state)) { @@ -71,7 +71,7 @@ int cpu_get_pic_interrupt(CPUX86State *env) X86CPU *cpu = env_archcpu(env); int intno; - if (!kvm_irqchip_in_kernel() && !whpx_apic_in_platform()) { + if (!kvm_irqchip_in_kernel() && !whpx_irqchip_in_kernel()) { intno = apic_get_interrupt(cpu->apic_state); if (intno >= 0) { return intno; diff --git a/hw/intc/arm_gicv3_common.c b/hw/intc/arm_gicv3_common.c index 0a2e5a3e2f..9054143ea7 100644 --- a/hw/intc/arm_gicv3_common.c +++ b/hw/intc/arm_gicv3_common.c @@ -32,6 +32,7 @@ #include "gicv3_internal.h" #include "hw/arm/linux-boot-if.h" #include "system/kvm.h" +#include "system/whpx.h" static void gicv3_gicd_no_migration_shift_bug_post_load(GICv3State *cs) @@ -663,6 +664,8 @@ const char *gicv3_class_name(void) { if (kvm_irqchip_in_kernel()) { return "kvm-arm-gicv3"; + } else if (whpx_enabled()) { + return TYPE_WHPX_GICV3; } else { if (kvm_enabled()) { error_report("Userspace GICv3 is not supported with KVM"); diff --git a/hw/intc/arm_gicv3_whpx.c b/hw/intc/arm_gicv3_whpx.c new file mode 100644 index 0000000000..849a005242 --- /dev/null +++ b/hw/intc/arm_gicv3_whpx.c @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ARM Generic Interrupt Controller using HVF platform support + * + * Copyright (c) 2025 Mohamed Mediouni + * Based on vGICv3 KVM code by Pavel Fedin + * + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/intc/arm_gicv3_common.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "system/runstate.h" +#include "system/whpx.h" +#include "system/whpx-internal.h" +#include "gicv3_internal.h" +#include "vgic_common.h" +#include "migration/blocker.h" +#include "qom/object.h" +#include "target/arm/cpregs.h" + +#include "hw/arm/bsa.h" +#include <winhvplatform.h> +#include <winhvplatformdefs.h> +#include <winnt.h> + +struct WHPXARMGICv3Class { + ARMGICv3CommonClass parent_class; + DeviceRealize parent_realize; + ResettablePhases parent_phases; +}; + +OBJECT_DECLARE_TYPE(GICv3State, WHPXARMGICv3Class, WHPX_GICV3) + +/* TODO: Implement GIC state save-restore */ +static void whpx_gicv3_check(GICv3State *s) +{ +} + +static void whpx_gicv3_put(GICv3State *s) +{ + whpx_gicv3_check(s); +} + +static void whpx_gicv3_get(GICv3State *s) +{ +} + +static void whpx_gicv3_set_irq(void *opaque, int irq, int level) +{ + struct whpx_state *whpx = &whpx_global; + GICv3State *s = opaque; + WHV_INTERRUPT_CONTROL interrupt_control = { + .InterruptControl.InterruptType = WHvArm64InterruptTypeFixed, + .RequestedVector = GIC_INTERNAL + irq, + .InterruptControl.Asserted = level + }; + + if (irq > s->num_irq) { + return; + } + + + whp_dispatch.WHvRequestInterrupt(whpx->partition, &interrupt_control, + sizeof(interrupt_control)); +} + +static void whpx_gicv3_icc_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + GICv3CPUState *c; + + c = env->gicv3state; + + c->icc_pmr_el1 = 0; + /* + * Architecturally the reset value of the ICC_BPR registers + * is UNKNOWN. We set them all to 0 here; when the kernel + * uses these values to program the ICH_VMCR_EL2 fields that + * determine the guest-visible ICC_BPR register values, the + * hardware's "writing a value less than the minimum sets + * the field to the minimum value" behaviour will result in + * them effectively resetting to the correct minimum value + * for the host GIC. + */ + c->icc_bpr[GICV3_G0] = 0; + c->icc_bpr[GICV3_G1] = 0; + c->icc_bpr[GICV3_G1NS] = 0; + + c->icc_sre_el1 = 0x7; + memset(c->icc_apr, 0, sizeof(c->icc_apr)); + memset(c->icc_igrpen, 0, sizeof(c->icc_igrpen)); +} + +static void whpx_gicv3_reset_hold(Object *obj, ResetType type) +{ + GICv3State *s = ARM_GICV3_COMMON(obj); + WHPXARMGICv3Class *kgc = WHPX_GICV3_GET_CLASS(s); + + if (kgc->parent_phases.hold) { + kgc->parent_phases.hold(obj, type); + } + + whpx_gicv3_put(s); +} + + +/* + * CPU interface registers of GIC needs to be reset on CPU reset. + * For the calling arm_gicv3_icc_reset() on CPU reset, we register + * below ARMCPRegInfo. As we reset the whole cpu interface under single + * register reset, we define only one register of CPU interface instead + * of defining all the registers. + */ +static const ARMCPRegInfo gicv3_cpuif_reginfo[] = { + { .name = "ICC_CTLR_EL1", .state = ARM_CP_STATE_BOTH, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 12, .opc2 = 4, + /* + * If ARM_CP_NOP is used, resetfn is not called, + * So ARM_CP_NO_RAW is appropriate type. + */ + .type = ARM_CP_NO_RAW, + .access = PL1_RW, + .readfn = arm_cp_read_zero, + .writefn = arm_cp_write_ignore, + /* + * We hang the whole cpu interface reset routine off here + * rather than parcelling it out into one little function + * per register + */ + .resetfn = whpx_gicv3_icc_reset, + }, +}; + +static void whpx_set_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE val) +{ + struct whpx_state *whpx = &whpx_global; + HRESULT hr; + + hr = whp_dispatch.WHvSetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index, + ®, 1, &val); + + if (FAILED(hr)) { + error_report("WHPX: Failed to set register %08x, hr=%08lx", reg, hr); + } +} + +static void whpx_gicv3_realize(DeviceState *dev, Error **errp) +{ + ERRP_GUARD(); + GICv3State *s = WHPX_GICV3(dev); + WHPXARMGICv3Class *kgc = WHPX_GICV3_GET_CLASS(s); + int i; + + kgc->parent_realize(dev, errp); + if (*errp) { + return; + } + + if (s->revision != 3) { + error_setg(errp, "unsupported GIC revision %d for platform GIC", + s->revision); + return; + } + + if (s->security_extn) { + error_setg(errp, "the platform vGICv3 does not implement the " + "security extensions"); + return; + } + + if (s->nmi_support) { + error_setg(errp, "NMI is not supported with the platform GIC"); + return; + } + + if (s->nb_redist_regions > 1) { + error_setg(errp, "Multiple VGICv3 redistributor regions are not " + "supported by WHPX"); + error_append_hint(errp, "A maximum of %d VCPUs can be used", + s->redist_region_count[0]); + return; + } + + gicv3_init_irqs_and_mmio(s, whpx_gicv3_set_irq, NULL); + + for (i = 0; i < s->num_cpu; i++) { + CPUState *cpu_state = qemu_get_cpu(i); + ARMCPU *cpu = ARM_CPU(cpu_state); + WHV_REGISTER_VALUE val = {.Reg64 = 0x080A0000 + (GICV3_REDIST_SIZE * i)}; + whpx_set_reg(cpu_state, WHvArm64RegisterGicrBaseGpa, val); + define_arm_cp_regs(cpu, gicv3_cpuif_reginfo); + } + + if (s->maint_irq) { + error_setg(errp, "Nested virtualisation not currently supported by WHPX."); + return; + } + + error_setg(&s->migration_blocker, + "Live migration disabled because GIC state save/restore not supported on WHPX"); + if (migrate_add_blocker(&s->migration_blocker, errp) < 0) { + error_report_err(*errp); + } +} + +static void whpx_gicv3_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + ARMGICv3CommonClass *agcc = ARM_GICV3_COMMON_CLASS(klass); + WHPXARMGICv3Class *kgc = WHPX_GICV3_CLASS(klass); + + agcc->pre_save = whpx_gicv3_get; + agcc->post_load = whpx_gicv3_put; + + device_class_set_parent_realize(dc, whpx_gicv3_realize, + &kgc->parent_realize); + resettable_class_set_parent_phases(rc, NULL, whpx_gicv3_reset_hold, NULL, + &kgc->parent_phases); +} + +static const TypeInfo whpx_arm_gicv3_info = { + .name = TYPE_WHPX_GICV3, + .parent = TYPE_ARM_GICV3_COMMON, + .instance_size = sizeof(GICv3State), + .class_init = whpx_gicv3_class_init, + .class_size = sizeof(WHPXARMGICv3Class), +}; + +static void whpx_gicv3_register_types(void) +{ + type_register_static(&whpx_arm_gicv3_info); +} + +type_init(whpx_gicv3_register_types) diff --git a/hw/intc/meson.build b/hw/intc/meson.build index faae20b93d..96742df090 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -41,6 +41,7 @@ specific_ss.add(when: 'CONFIG_APIC', if_true: files('apic.c', 'apic_common.c')) arm_common_ss.add(when: 'CONFIG_ARM_GIC', if_true: files('arm_gicv3_cpuif_common.c')) arm_common_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files('arm_gicv3_cpuif.c')) specific_ss.add(when: 'CONFIG_ARM_GIC_KVM', if_true: files('arm_gic_kvm.c')) +specific_ss.add(when: ['CONFIG_WHPX', 'TARGET_AARCH64'], if_true: files('arm_gicv3_whpx.c')) specific_ss.add(when: ['CONFIG_ARM_GIC_KVM', 'TARGET_AARCH64'], if_true: files('arm_gicv3_kvm.c', 'arm_gicv3_its_kvm.c')) arm_common_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_nvic.c')) specific_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_irqmp.c')) diff --git a/include/hw/intc/arm_gicv3_common.h b/include/hw/intc/arm_gicv3_common.h index 3d24ad22d2..c55cf18120 100644 --- a/include/hw/intc/arm_gicv3_common.h +++ b/include/hw/intc/arm_gicv3_common.h @@ -313,6 +313,9 @@ typedef struct ARMGICv3CommonClass ARMGICv3CommonClass; DECLARE_OBJ_CHECKERS(GICv3State, ARMGICv3CommonClass, ARM_GICV3_COMMON, TYPE_ARM_GICV3_COMMON) +/* Types for GICv3 kernel-irqchip */ +#define TYPE_WHPX_GICV3 "whpx-arm-gicv3" + struct ARMGICv3CommonClass { /*< private >*/ SysBusDeviceClass parent_class; diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h index 609d0e1c08..8ded54a39b 100644 --- a/include/system/whpx-internal.h +++ b/include/system/whpx-internal.h @@ -45,7 +45,6 @@ struct whpx_state { bool kernel_irqchip_allowed; bool kernel_irqchip_required; - bool apic_in_platform; }; extern struct whpx_state whpx_global; diff --git a/include/system/whpx.h b/include/system/whpx.h index 00f6a3e523..4217a27e91 100644 --- a/include/system/whpx.h +++ b/include/system/whpx.h @@ -25,11 +25,12 @@ #ifdef CONFIG_WHPX_IS_POSSIBLE extern bool whpx_allowed; +extern bool whpx_irqchip_in_kernel; #define whpx_enabled() (whpx_allowed) -bool whpx_apic_in_platform(void); +#define whpx_irqchip_in_kernel() (whpx_irqchip_in_kernel) #else /* !CONFIG_WHPX_IS_POSSIBLE */ #define whpx_enabled() 0 -#define whpx_apic_in_platform() (0) +#define whpx_irqchip_in_kernel() (0) #endif /* !CONFIG_WHPX_IS_POSSIBLE */ #endif /* QEMU_WHPX_H */ diff --git a/target/i386/cpu-apic.c b/target/i386/cpu-apic.c index f7ad7b5139..eaa10ad2a3 100644 --- a/target/i386/cpu-apic.c +++ b/target/i386/cpu-apic.c @@ -33,7 +33,7 @@ APICCommonClass *apic_get_class(Error **errp) apic_type = "kvm-apic"; } else if (xen_enabled()) { apic_type = "xen-apic"; - } else if (whpx_apic_in_platform()) { + } else if (whpx_irqchip_in_kernel()) { apic_type = "whpx-apic"; } diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index 052cda42bf..8210250dc3 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -607,7 +607,7 @@ void whpx_get_registers(CPUState *cpu) hr); } - if (whpx_apic_in_platform()) { + if (whpx_irqchip_in_kernel()) { /* * Fetch the TPR value from the emulated APIC. It may get overwritten * below with the value from CR8 returned by @@ -749,7 +749,7 @@ void whpx_get_registers(CPUState *cpu) assert(idx == RTL_NUMBER_OF(whpx_register_names)); - if (whpx_apic_in_platform()) { + if (whpx_irqchip_in_kernel()) { whpx_apic_get(x86_cpu->apic_state); } @@ -1379,7 +1379,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu) } /* Get pending hard interruption or replay one that was overwritten */ - if (!whpx_apic_in_platform()) { + if (!whpx_irqchip_in_kernel()) { if (!vcpu->interruption_pending && vcpu->interruptable && (env->eflags & IF_MASK)) { assert(!new_int.InterruptionPending); @@ -1553,7 +1553,7 @@ int whpx_vcpu_run(CPUState *cpu) if (exclusive_step_mode == WHPX_STEP_NONE) { whpx_vcpu_process_async_events(cpu); - if (cpu->halted && !whpx_apic_in_platform()) { + if (cpu->halted && !whpx_irqchip_in_kernel()) { cpu->exception_index = EXCP_HLT; qatomic_set(&cpu->exit_request, false); return 0; @@ -1642,7 +1642,7 @@ int whpx_vcpu_run(CPUState *cpu) break; case WHvRunVpExitReasonX64ApicEoi: - assert(whpx_apic_in_platform()); + assert(whpx_irqchip_in_kernel()); ioapic_eoi_broadcast(vcpu->exit_ctx.ApicEoi.InterruptVector); break; @@ -2187,7 +2187,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms) goto error; } } else { - whpx->apic_in_platform = true; + whpx_irqchip_in_kernel = true; } } @@ -2196,7 +2196,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms) prop.ExtendedVmExits.X64MsrExit = 1; prop.ExtendedVmExits.X64CpuidExit = 1; prop.ExtendedVmExits.ExceptionExit = 1; - if (whpx_apic_in_platform()) { + if (whpx_irqchip_in_kernel()) { prop.ExtendedVmExits.X64ApicInitSipiExitTrap = 1; } -- 2.50.1 (Apple Git-155)
