diff mbox series

[RFC,v5,28/52] i386/xen: Add support for Xen event channel delivery to vCPU

Message ID 20221230121235.1282915-29-dwmw2@infradead.org
State New
Headers show
Series Xen support under KVM | expand

Commit Message

David Woodhouse Dec. 30, 2022, 12:12 p.m. UTC
From: David Woodhouse <dwmw@amazon.co.uk>

The kvm_xen_inject_vcpu_callback_vector() function will either deliver
the per-vCPU local APIC vector (as an MSI), or just kick the vCPU out
of the kernel to trigger KVM's automatic delivery of the global vector.
Support for asserting the GSI/PCI_INTX callbacks will come later.

Also add kvm_xen_get_vcpu_info_hva() which returns the vcpu_info of
a given vCPU.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 include/sysemu/kvm_xen.h  |  2 ++
 target/i386/kvm/xen-emu.c | 68 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)

Comments

David Woodhouse Jan. 9, 2023, 9:18 p.m. UTC | #1
On Fri, 2022-12-30 at 12:12 +0000, David Woodhouse wrote:
> 
> +static void *gpa_to_hva(uint64_t gpa)
> +{
> +    MemoryRegionSection mrs;
> +
> +    mrs = memory_region_find(get_system_memory(), gpa, 1);
> +    return !mrs.mr ? NULL : qemu_map_ram_ptr(mrs.mr->ram_block,
> +                                             mrs.offset_within_region);
> +}
> +
> +void *kvm_xen_get_vcpu_info_hva(uint32_t vcpu_id)
> +{
> +    CPUState *cs = qemu_get_cpu(vcpu_id);
> +    CPUX86State *env;
> +    uint64_t gpa;
> +
> +    if (!cs) {
> +        return NULL;
> +    }
> +    env = &X86_CPU(cs)->env;
> +
> +    gpa = env->xen_vcpu_info_gpa;
> +    if (gpa == INVALID_GPA) {
> +        gpa = env->xen_vcpu_info_default_gpa;
> +    }
> +    if (gpa == INVALID_GPA) {
> +        return NULL;
> +    }
> +
> +    return gpa_to_hva(gpa);
> +}

Hm, no. That leaks a refcount on the MemoryRegion each time, doesn't
it?

Fixing it up something like this, by caching the HVA (and the
MemoryRegion so we can unref it later)... 

I'll send the incremental patch to allow for specific review without it
getting lost in the noise:

diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 1a411e1b49..b579f0f0f8 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -1791,6 +1791,8 @@ typedef struct CPUArchState {
 #endif
 #if defined(CONFIG_KVM)
     struct kvm_nested_state *nested_state;
+    MemoryRegion *xen_vcpu_info_mr;
+    void *xen_vcpu_info_hva;
     uint64_t xen_vcpu_info_gpa;
     uint64_t xen_vcpu_info_default_gpa;
     uint64_t xen_vcpu_time_info_gpa;
diff --git a/target/i386/kvm/xen-emu.c b/target/i386/kvm/xen-emu.c
index 6c0f180059..aa2c00e0b9 100644
--- a/target/i386/kvm/xen-emu.c
+++ b/target/i386/kvm/xen-emu.c
@@ -320,6 +320,35 @@ static void do_set_vcpu_callback_vector(CPUState *cs, run_on_cpu_data data)
     }
 }
 
+static int set_vcpu_info(CPUState *cs, uint64_t gpa)
+{
+    X86CPU *cpu = X86_CPU(cs);
+    CPUX86State *env = &cpu->env;
+    MemoryRegionSection mrs;
+    int ret;
+
+    ret = kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO, gpa);
+    if (ret || gpa == INVALID_GPA) {
+    fail:
+        if (env->xen_vcpu_info_mr) {
+            memory_region_unref(env->xen_vcpu_info_mr);
+            env->xen_vcpu_info_mr = NULL;
+        }
+        env->xen_vcpu_info_hva = NULL;
+        return ret;
+    }
+
+    mrs = memory_region_find(get_system_memory(), gpa, sizeof(struct vcpu_info));
+    if (!mrs.mr || !mrs.mr->ram_block || mrs.size < sizeof(struct vcpu_info) ||
+        !(env->xen_vcpu_info_hva = qemu_map_ram_ptr(mrs.mr->ram_block,
+                                                    mrs.offset_within_region))) {
+        ret = -EINVAL;
+        goto fail;
+    }
+    env->xen_vcpu_info_mr = mrs.mr;
+    return 0;
+}
+
 static void do_set_vcpu_info_default_gpa(CPUState *cs, run_on_cpu_data data)
 {
     X86CPU *cpu = X86_CPU(cs);
@@ -329,8 +358,7 @@ static void do_set_vcpu_info_default_gpa(CPUState *cs, run_on_cpu_data data)
 
     /* Changing the default does nothing if a vcpu_info was explicitly set. */
     if (env->xen_vcpu_info_gpa == INVALID_GPA) {
-        kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO,
-                              env->xen_vcpu_info_default_gpa);
+        set_vcpu_info(cs, env->xen_vcpu_info_default_gpa);
     }
 }
 
@@ -341,55 +369,30 @@ static void do_set_vcpu_info_gpa(CPUState *cs, run_on_cpu_data data)
 
     env->xen_vcpu_info_gpa = data.host_ulong;
 
-    kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO,
-                          env->xen_vcpu_info_gpa);
-}
-
-static void *gpa_to_hva(uint64_t gpa)
-{
-    MemoryRegionSection mrs;
-
-    mrs = memory_region_find(get_system_memory(), gpa, 1);
-    return !mrs.mr ? NULL : qemu_map_ram_ptr(mrs.mr->ram_block,
-                                             mrs.offset_within_region);
-}
-
-static void *vcpu_info_hva_from_cs(CPUState *cs)
-{
-    CPUX86State *env = &X86_CPU(cs)->env;
-    uint64_t gpa = env->xen_vcpu_info_gpa;
-
-    if (gpa == INVALID_GPA) {
-        gpa = env->xen_vcpu_info_default_gpa;
-    }
-    if (gpa == INVALID_GPA) {
-        return NULL;
-    }
-
-    return gpa_to_hva(gpa);
+    set_vcpu_info(cs, env->xen_vcpu_info_gpa);
 }
 
 void *kvm_xen_get_vcpu_info_hva(uint32_t vcpu_id)
 {
     CPUState *cs = qemu_get_cpu(vcpu_id);
-
     if (!cs) {
             return NULL;
     }
 
-    return vcpu_info_hva_from_cs(cs);
+    return X86_CPU(cs)->env.xen_vcpu_info_hva;
 }
 
 void kvm_xen_maybe_deassert_callback(CPUState *cs)
 {
-    struct vcpu_info *vi = vcpu_info_hva_from_cs(cs);
+    CPUX86State *env = &X86_CPU(cs)->env;
+    struct vcpu_info *vi = env->xen_vcpu_info_hva;
     if (!vi) {
             return;
     }
 
     /* If the evtchn_upcall_pending flag is cleared, turn the GSI off. */
     if (!vi->evtchn_upcall_pending) {
-        X86_CPU(cs)->env.xen_callback_asserted = false;
+        env->xen_callback_asserted = false;
         xen_evtchn_set_callback_level(0);
     }
 }
@@ -519,7 +522,7 @@ static void do_vcpu_soft_reset(CPUState *cs, run_on_cpu_data data)
     env->xen_singleshot_timer_ns = 0;
     memset(env->xen_virq, 0, sizeof(env->xen_virq));
 
-    kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO, INVALID_GPA);
+    set_vcpu_info(cs, INVALID_GPA);
     kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_TIME_INFO,
                           INVALID_GPA);
     kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_ADDR,
@@ -1572,7 +1575,7 @@ int kvm_put_xen_state(CPUState *cs)
     }
 
     if (gpa != INVALID_GPA) {
-        ret = kvm_xen_set_vcpu_attr(cs, KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO, gpa);
+        ret = set_vcpu_info(cs, gpa);
         if (ret < 0) {
             return ret;
         }
diff mbox series

Patch

diff --git a/include/sysemu/kvm_xen.h b/include/sysemu/kvm_xen.h
index 3e43cd7843..ee53294deb 100644
--- a/include/sysemu/kvm_xen.h
+++ b/include/sysemu/kvm_xen.h
@@ -17,6 +17,8 @@ 
 #define INVALID_GFN UINT64_MAX
 
 uint32_t kvm_xen_get_caps(void);
+void *kvm_xen_get_vcpu_info_hva(uint32_t vcpu_id);
+void kvm_xen_inject_vcpu_callback_vector(uint32_t vcpu_id, int type);
 
 #define kvm_xen_has_cap(cap) (!!(kvm_xen_get_caps() &           \
                                  KVM_XEN_HVM_CONFIG_ ## cap))
diff --git a/target/i386/kvm/xen-emu.c b/target/i386/kvm/xen-emu.c
index 87fcfc636c..11a6fe4164 100644
--- a/target/i386/kvm/xen-emu.c
+++ b/target/i386/kvm/xen-emu.c
@@ -22,6 +22,8 @@ 
 #include "trace.h"
 #include "sysemu/runstate.h"
 
+#include "hw/pci/msi.h"
+#include "hw/i386/apic-msidef.h"
 #include "hw/i386/kvm/xen_overlay.h"
 #include "hw/i386/kvm/xen_evtchn.h"
 
@@ -274,6 +276,72 @@  static void do_set_vcpu_info_gpa(CPUState *cs, run_on_cpu_data data)
                           env->xen_vcpu_info_gpa);
 }
 
+static void *gpa_to_hva(uint64_t gpa)
+{
+    MemoryRegionSection mrs;
+
+    mrs = memory_region_find(get_system_memory(), gpa, 1);
+    return !mrs.mr ? NULL : qemu_map_ram_ptr(mrs.mr->ram_block,
+                                             mrs.offset_within_region);
+}
+
+void *kvm_xen_get_vcpu_info_hva(uint32_t vcpu_id)
+{
+    CPUState *cs = qemu_get_cpu(vcpu_id);
+    CPUX86State *env;
+    uint64_t gpa;
+
+    if (!cs) {
+        return NULL;
+    }
+    env = &X86_CPU(cs)->env;
+
+    gpa = env->xen_vcpu_info_gpa;
+    if (gpa == INVALID_GPA) {
+        gpa = env->xen_vcpu_info_default_gpa;
+    }
+    if (gpa == INVALID_GPA) {
+        return NULL;
+    }
+
+    return gpa_to_hva(gpa);
+}
+
+void kvm_xen_inject_vcpu_callback_vector(uint32_t vcpu_id, int type)
+{
+    CPUState *cs = qemu_get_cpu(vcpu_id);
+    uint8_t vector;
+
+    if (!cs) {
+        return;
+    }
+
+    vector = X86_CPU(cs)->env.xen_vcpu_callback_vector;
+    if (vector) {
+        /*
+         * The per-vCPU callback vector injected via lapic. Just
+         * deliver it as an MSI.
+         */
+        MSIMessage msg = {
+            .address = APIC_DEFAULT_ADDRESS | X86_CPU(cs)->apic_id,
+            .data = vector | (1UL << MSI_DATA_LEVEL_SHIFT),
+        };
+        kvm_irqchip_send_msi(kvm_state, msg);
+        return;
+    }
+
+    switch (type) {
+    case HVM_PARAM_CALLBACK_TYPE_VECTOR:
+        /*
+         * If the evtchn_upcall_pending field in the vcpu_info is set, then
+         * KVM will automatically deliver the vector on entering the vCPU
+         * so all we have to do is kick it out.
+         */
+        qemu_cpu_kick(cs);
+        break;
+    }
+}
+
 static void do_set_vcpu_time_info_gpa(CPUState *cs, run_on_cpu_data data)
 {
     X86CPU *cpu = X86_CPU(cs);