KVM: PPC: Add level based interrupt logic
authorAlexander Graf <agraf@suse.de>
Mon, 30 Aug 2010 11:49:15 +0000 (13:49 +0200)
committerAlexander Graf <agraf@suse.de>
Sun, 5 Sep 2010 09:50:48 +0000 (11:50 +0200)
KVM on PowerPC used to have completely broken interrupt logic. Usually,
interrupts work by having a PIC that pulls a line up/down, so the CPU knows
that an interrupt is active. This line stays active until some action is
done to the PIC to release the line.

On KVM for PPC, we just checked if there was an interrupt pending and pulled
a line in the kernel module. We never released it though, hoping that kernel
space would just declare an interrupt as released when injected - which is
wrong.

To fix this, we need to completely redesign the interrupt injection logic.
Whenever an interrupt line gets triggered, we need to notify kernel space
that the line is up. Whenever it gets released, we do the same. This way
we can assure that the interrupt state is always known to kernel space.

This fixes random stalls in KVM guests on PowerPC that were waiting for
an interrupt while everyone else thought they received it already.

Signed-off-by: Alexander Graf <agraf@suse.de>
hw/ppc.c
target-ppc/kvm.c
target-ppc/kvm_ppc.h

index 2a77eb9bffa1707ed5560a14cafd71462265d89c..55e380844c005064814fba5828da6dd280341dc0 100644 (file)
--- a/hw/ppc.c
+++ b/hw/ppc.c
@@ -28,6 +28,8 @@
 #include "nvram.h"
 #include "qemu-log.h"
 #include "loader.h"
+#include "kvm.h"
+#include "kvm_ppc.h"
 
 //#define PPC_DEBUG_IRQ
 //#define PPC_DEBUG_TB
@@ -50,6 +52,8 @@ static void cpu_ppc_tb_start (CPUState *env);
 
 static void ppc_set_irq (CPUState *env, int n_IRQ, int level)
 {
+    unsigned int old_pending = env->pending_interrupts;
+
     if (level) {
         env->pending_interrupts |= 1 << n_IRQ;
         cpu_interrupt(env, CPU_INTERRUPT_HARD);
@@ -58,6 +62,13 @@ static void ppc_set_irq (CPUState *env, int n_IRQ, int level)
         if (env->pending_interrupts == 0)
             cpu_reset_interrupt(env, CPU_INTERRUPT_HARD);
     }
+
+    if (old_pending != env->pending_interrupts) {
+#ifdef CONFIG_KVM
+        kvmppc_set_interrupt(env, n_IRQ, level);
+#endif
+    }
+
     LOG_IRQ("%s: %p n_IRQ %d level %d => pending %08" PRIx32
                 "req %08x\n", __func__, env, n_IRQ, level,
                 env->pending_interrupts, env->interrupt_request);
index 14d6365ee254c4bea735a44b6024920d18bcfd03..5cacef7b5806b791ec2d48f88917b2c1d880c58b 100644 (file)
@@ -37,6 +37,9 @@
     do { } while (0)
 #endif
 
+static int cap_interrupt_unset = false;
+static int cap_interrupt_level = false;
+
 /* XXX We have a race condition where we actually have a level triggered
  *     interrupt, but the infrastructure can't expose that yet, so the guest
  *     takes but ignores it, goes to sleep and never gets notified that there's
@@ -55,6 +58,18 @@ static void kvm_kick_env(void *env)
 
 int kvm_arch_init(KVMState *s, int smp_cpus)
 {
+#ifdef KVM_CAP_PPC_UNSET_IRQ
+    cap_interrupt_unset = kvm_check_extension(s, KVM_CAP_PPC_UNSET_IRQ);
+#endif
+#ifdef KVM_CAP_PPC_IRQ_LEVEL
+    cap_interrupt_level = kvm_check_extension(s, KVM_CAP_PPC_IRQ_LEVEL);
+#endif
+
+    if (!cap_interrupt_level) {
+        fprintf(stderr, "KVM: Couldn't find level irq capability. Expect the "
+                        "VM to stall at times!\n");
+    }
+
     return 0;
 }
 
@@ -178,6 +193,23 @@ int kvm_arch_get_registers(CPUState *env)
     return 0;
 }
 
+int kvmppc_set_interrupt(CPUState *env, int irq, int level)
+{
+    unsigned virq = level ? KVM_INTERRUPT_SET_LEVEL : KVM_INTERRUPT_UNSET;
+
+    if (irq != PPC_INTERRUPT_EXT) {
+        return 0;
+    }
+
+    if (!kvm_enabled() || !cap_interrupt_unset || !cap_interrupt_level) {
+        return 0;
+    }
+
+    kvm_vcpu_ioctl(env, KVM_INTERRUPT, &virq);
+
+    return 0;
+}
+
 #if defined(TARGET_PPCEMB)
 #define PPC_INPUT_INT PPC40x_INPUT_INT
 #elif defined(TARGET_PPC64)
@@ -193,7 +225,8 @@ int kvm_arch_pre_run(CPUState *env, struct kvm_run *run)
 
     /* PowerPC Qemu tracks the various core input pins (interrupt, critical
      * interrupt, reset, etc) in PPC-specific env->irq_input_state. */
-    if (run->ready_for_interrupt_injection &&
+    if (!cap_interrupt_level &&
+        run->ready_for_interrupt_injection &&
         (env->interrupt_request & CPU_INTERRUPT_HARD) &&
         (env->irq_input_state & (1<<PPC_INPUT_INT)))
     {
@@ -201,7 +234,7 @@ int kvm_arch_pre_run(CPUState *env, struct kvm_run *run)
          * future KVM could cache it in-kernel to avoid a heavyweight exit
          * when reading the UIC.
          */
-        irq = -1U;
+        irq = KVM_INTERRUPT_SET;
 
         dprintf("injected interrupt %d\n", irq);
         r = kvm_vcpu_ioctl(env, KVM_INTERRUPT, &irq);
index 65e31c9b9e1ead8672b652d69d1268078ccfc3ca..911b19e378b499a1a4b5d8381c1aa5e8640e2cc6 100644 (file)
@@ -16,5 +16,18 @@ int kvmppc_read_host_property(const char *node_path, const char *prop,
 
 uint32_t kvmppc_get_tbfreq(void);
 int kvmppc_get_hypercall(CPUState *env, uint8_t *buf, int buf_len);
+int kvmppc_set_interrupt(CPUState *env, int irq, int level);
+
+#ifndef KVM_INTERRUPT_SET
+#define KVM_INTERRUPT_SET -1
+#endif
+
+#ifndef KVM_INTERRUPT_UNSET
+#define KVM_INTERRUPT_UNSET -2
+#endif
+
+#ifndef KVM_INTERRUPT_SET_LEVEL
+#define KVM_INTERRUPT_SET_LEVEL -3
+#endif
 
 #endif /* __KVM_PPC_H__ */