To inject legacy interrupts in the MSHV accelerator, we have to request a "deliverability notification" once the PIC asserts an interrupt. Once we receive a INTERRUPTION_DELIVERABLE exit from the hypervisor we can proceed to acknowledge the interrupt and write to the PENDING_EVENT0 register with EXT_INT event type and the respective vector.
Since an interrupt is supposed to wake a vCPU stopped by HLT, we do clear the halt_suspend field of the INTERNAL_ACTIVITY_STATE register. We also need to consider pending EXT_INT events in migration, splitting the mapping of pending_exc into two discrete EXCEPTION and EXT_INT event types. Signed-off-by: Magnus Kulke <[email protected]> --- hw/i386/x86-cpu.c | 9 +- include/hw/hyperv/hvgdk_mini.h | 42 ++++++++- include/system/mshv_int.h | 1 + meson.build | 1 + target/i386/mshv/mshv-cpu.c | 155 +++++++++++++++++++++++++++++++-- target/i386/mshv/trace-events | 7 ++ target/i386/mshv/trace.h | 2 + 7 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 target/i386/mshv/trace-events create mode 100644 target/i386/mshv/trace.h diff --git a/hw/i386/x86-cpu.c b/hw/i386/x86-cpu.c index fba313376c..7b9299272a 100644 --- a/hw/i386/x86-cpu.c +++ b/hw/i386/x86-cpu.c @@ -46,12 +46,8 @@ static void pic_irq_request(void *opaque, int irq, int level) trace_x86_pic_interrupt(irq, level); - if (mshv_irqchip_in_kernel()) { - return; - } - if (cpu_is_apic_enabled(cpu->apic_state) && !kvm_irqchip_in_kernel() && - !whpx_irqchip_in_kernel()) { + !mshv_irqchip_in_kernel() && !whpx_irqchip_in_kernel()) { CPU_FOREACH(cs) { cpu = X86_CPU(cs); if (apic_accept_pic_intr(cpu->apic_state)) { @@ -77,7 +73,8 @@ int cpu_get_pic_interrupt(CPUX86State *env) X86CPU *cpu = env_archcpu(env); int intno; - if (!kvm_irqchip_in_kernel() && !whpx_irqchip_in_kernel()) { + if (!kvm_irqchip_in_kernel() && !mshv_irqchip_in_kernel() && + !whpx_irqchip_in_kernel()) { intno = apic_get_interrupt(cpu->apic_state); if (intno >= 0) { return intno; diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h index 0983e3a58d..e585661b2c 100644 --- a/include/hw/hyperv/hvgdk_mini.h +++ b/include/hw/hyperv/hvgdk_mini.h @@ -30,10 +30,11 @@ typedef enum hv_register_name { HV_REGISTER_INTERNAL_ACTIVITY_STATE = 0x00000004, /* Pending Interruption Register */ - HV_REGISTER_PENDING_INTERRUPTION = 0x00010002, - HV_REGISTER_INTERRUPT_STATE = 0x00010003, - HV_REGISTER_PENDING_EVENT0 = 0x00010004, - HV_REGISTER_PENDING_EVENT1 = 0x00010005, + HV_REGISTER_PENDING_INTERRUPTION = 0x00010002, + HV_REGISTER_INTERRUPT_STATE = 0x00010003, + HV_REGISTER_PENDING_EVENT0 = 0x00010004, + HV_REGISTER_PENDING_EVENT1 = 0x00010005, + HV_REGISTER_DELIVERABILITY_NOTIFICATIONS = 0x00010006, /* X64 User-Mode Registers */ HV_X64_REGISTER_RAX = 0x00020000, @@ -313,6 +314,21 @@ union hv_x64_pending_exception_event { }; }; +#define HV_X64_PENDING_EVENT_EXCEPTION 0 +#define HV_X64_PENDING_EVENT_EXT_INT 5 + +union hv_x64_pending_ext_int_event { + uint64_t as_uint64[2]; + struct { + uint64_t event_pending:1; + uint64_t event_type:3; + uint64_t reserved0:4; + uint64_t vector:8; + uint64_t reserved1:48; + uint64_t reserved2; + }; +}; + union hv_x64_pending_virtualization_fault_event { uint64_t as_uint64[2]; struct { @@ -443,6 +459,17 @@ union hv_x64_interrupt_state_register { }; }; +union hv_x64_deliverability_notifications_register { + uint64_t as_uint64; + struct { + uint64_t nmi_notification:1; + uint64_t interrupt_notification:1; + uint64_t interrupt_priority:4; + uint64_t reserved:42; + uint64_t sints:16; + }; +}; + union hv_intercept_suspend_register { uint64_t as_uint64; struct { @@ -469,6 +496,7 @@ typedef union hv_register_value { union hv_x64_pending_interruption_register pending_interruption; union hv_x64_msr_npiep_config_contents npiep_config; union hv_x64_pending_exception_event pending_exception_event; + union hv_x64_pending_ext_int_event pending_ext_int_event; union hv_x64_pending_virtualization_fault_event pending_virtualization_fault_event; union hv_x64_register_sev_control sev_control; @@ -715,6 +743,12 @@ struct hv_x64_intercept_message_header { uint64_t rflags; } QEMU_PACKED; +struct hv_x64_interruption_deliverable_message { + struct hv_x64_intercept_message_header header; + uint32_t deliverable_type; + uint32_t reserved; +} QEMU_PACKED; + union hv_x64_io_port_access_info { uint8_t as_uint8; struct { diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index 1f77ffd152..d527cd8d77 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -45,6 +45,7 @@ typedef struct MshvHvCallArgs { struct AccelCPUState { int cpufd; MshvHvCallArgs hvcall_args; + bool pic_interrupt_window; }; typedef struct MshvMemoryListener { diff --git a/meson.build b/meson.build index bbef9d9c29..24caec97a9 100644 --- a/meson.build +++ b/meson.build @@ -3686,6 +3686,7 @@ if have_system or have_user 'target/hppa', 'target/i386', 'target/i386/kvm', + 'target/i386/mshv', 'target/i386/whpx', 'target/loongarch', 'target/mips/tcg', diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index e3c155da2e..7032e0f6ac 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -771,6 +771,7 @@ static int get_vcpu_events(CPUState *cpu) union hv_x64_pending_interruption_register pending_int; union hv_x64_interrupt_state_register int_state; union hv_x64_pending_exception_event pending_exc; + union hv_x64_pending_ext_int_event pending_ext_int; int ret; ret = mshv_get_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); @@ -782,6 +783,7 @@ static int get_vcpu_events(CPUState *cpu) pending_int.as_uint64 = assocs[0].value.reg64; int_state.as_uint64 = assocs[1].value.reg64; pending_exc = assocs[2].value.pending_exception_event; + pending_ext_int = assocs[2].value.pending_ext_int_event; /* Clear previous state. injected ints/excs are blanked w/ -1 */ env->interrupt_injected = -1; @@ -841,14 +843,25 @@ static int get_vcpu_events(CPUState *cpu) env->hflags2 &= ~HF2_NMI_MASK; } - /* HV_REGISTER_PENDING_EVENT0: pending exception not yet injected */ + /* HV_REGISTER_PENDING_EVENT0: event not yet injected */ if (pending_exc.event_pending) { - env->exception_pending = 1; - env->exception_nr = pending_exc.vector; - env->has_error_code = pending_exc.deliver_error_code; - env->error_code = pending_exc.error_code; - env->exception_has_payload = (pending_exc.exception_parameter != 0); - env->exception_payload = pending_exc.exception_parameter; + switch (pending_exc.event_type) { + case HV_X64_PENDING_EVENT_EXCEPTION: + env->exception_pending = 1; + env->exception_nr = pending_exc.vector; + env->has_error_code = pending_exc.deliver_error_code; + env->error_code = pending_exc.error_code; + env->exception_has_payload = pending_exc.exception_parameter != 0; + env->exception_payload = pending_exc.exception_parameter; + break; + case HV_X64_PENDING_EVENT_EXT_INT: + env->interrupt_injected = pending_ext_int.vector; + break; + default: + error_report("unknown pending event type %u", + pending_exc.event_type); + return -EINVAL; + } } /* @@ -2091,6 +2104,119 @@ static int handle_exception_interrupt(CPUState *cpu, return 0; } +static int set_pic_interrupt_window(CPUState *cpu, bool enabled) +{ + union hv_x64_deliverability_notifications_register notifications = { 0 }; + struct hv_register_assoc assoc = { 0 }; + AccelCPUState *cpu_state = cpu->accel; + int ret; + + notifications.interrupt_notification = enabled; + + assoc.name = HV_REGISTER_DELIVERABILITY_NOTIFICATIONS; + assoc.value.reg64 = notifications.as_uint64; + + /* we are already waiting for a PIC interrupt window */ + if (cpu_state->pic_interrupt_window == enabled) { + return 0; + } + + trace_mshv_set_pic_interrupt_window(cpu->cpu_index, enabled); + + ret = mshv_set_generic_regs(cpu, &assoc, 1); + if (ret < 0) { + error_report("failed to set deliverability notifications register"); + return ret; + } + + cpu_state->pic_interrupt_window = enabled; + return 0; +} + +static inline int request_pic_interrupt_window(CPUState *cpu) +{ + return set_pic_interrupt_window(cpu, true); +} + +static inline int clear_pic_interrupt_window(CPUState *cpu) +{ + return set_pic_interrupt_window(cpu, false); +} + +static int handle_interrupt_deliverable(CPUState *cpu, + const struct hyperv_message *msg) +{ + CPUX86State *env = &X86_CPU(cpu)->env; + struct hv_x64_interruption_deliverable_message info; + union hv_x64_pending_ext_int_event event = { 0 }; + struct hv_register_assoc assocs[2] = { 0 }; + struct hv_register_assoc *event_assoc = &assocs[0], + *activity_assoc = &assocs[1]; + union hv_internal_activity_register activity; + bool is_suspended; + int ret, vector; + + memcpy(&info, msg->payload, sizeof(info)); + if (info.deliverable_type != MSHV_HV_INTERRUPTION_TYPE_EXT_INT) { + error_report("unexpected deliverable interruption type %u", + info.deliverable_type); + return -1; + } + + ret = clear_pic_interrupt_window(cpu); + if (ret < 0) { + error_report("failed to clear PIC interrupt window"); + return -1; + } + + bql_lock(); + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); + vector = cpu_get_pic_interrupt(env); + bql_unlock(); + + /* vector == -1 means the PIC has no longer a pending interrupt */ + if (vector < 0) { + return 0; + } + + trace_mshv_pic_interrupt(cpu->cpu_index, vector); + + /* we check whether the vcpu is halted first */ + activity_assoc->name = HV_REGISTER_INTERNAL_ACTIVITY_STATE; + ret = mshv_get_generic_regs(cpu, activity_assoc, 1); + if (ret < 0) { + error_report("failed to read activity state before PIC delivery"); + return -1; + } + + event.event_pending = 1; + event.event_type = HV_X64_PENDING_EVENT_EXT_INT; + event.vector = vector; + + event_assoc->name = HV_REGISTER_PENDING_EVENT0; + event_assoc->value.pending_ext_int_event = event; + + /* + * If the vcpu was hlt suspended we clear the suspend bit, otherwise we + * only set the pending event. + */ + activity = activity_assoc->value.internal_activity; + is_suspended = activity.halt_suspend; + if (is_suspended) { + activity.halt_suspend = 0; + activity_assoc->value.internal_activity = activity; + } + + ret = mshv_set_generic_regs(cpu, assocs, is_suspended ? 2 : 1); + if (ret < 0) { + error_report("failed to set pending event%s", + is_suspended ? " and activity state" : ""); + return ret; + } + + return 0; +} + /* * Flip RFLAGS.TF like WHPX. Set it only on the live register, not env->eflags, * so a later store won't put it back. @@ -2139,6 +2265,14 @@ int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit) } } + if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) { + ret = request_pic_interrupt_window(cpu); + if (ret < 0) { + error_report("failed to request PIC interrupt window"); + return -1; + } + } + ret = ioctl(cpu_fd, MSHV_RUN_VP, msg); if (ret < 0) { if (errno == EINTR) { @@ -2185,6 +2319,13 @@ int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit) return -1; } return 0; + case HVMSG_X64_INTERRUPTION_DELIVERABLE: + ret = handle_interrupt_deliverable(cpu, msg); + if (ret < 0) { + return ret; + } + *exit = MshvVmExitIgnore; + return 0; default: break; } diff --git a/target/i386/mshv/trace-events b/target/i386/mshv/trace-events new file mode 100644 index 0000000000..347ce040ab --- /dev/null +++ b/target/i386/mshv/trace-events @@ -0,0 +1,7 @@ +# Authors: Magnus Kulke <[email protected]> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +mshv_run_vcpu_eintr(uint32_t cpu) "cpu_index=%d" +mshv_set_pic_interrupt_window(uint32_t cpu, bool enabled) "cpu_index=%d enabled=%d" +mshv_pic_interrupt(uint32_t cpu, uint8_t vector) "cpu_index=%d vector=0x%x" diff --git a/target/i386/mshv/trace.h b/target/i386/mshv/trace.h new file mode 100644 index 0000000000..81b6b655c7 --- /dev/null +++ b/target/i386/mshv/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-target_i386_mshv.h" -- 2.34.1
