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


Reply via email to