kvm: x86: Add support for x86 INIT and STARTUP handling
authorAndreas Sandberg <andreas@sandberg.pp.se>
Sun, 16 Mar 2014 16:28:23 +0000 (17:28 +0100)
committerAndreas Sandberg <andreas@sandberg.pp.se>
Sun, 16 Mar 2014 16:28:23 +0000 (17:28 +0100)
This changeset adds support for INIT and STARTUP IPI handling. We
currently handle both of these interrupts in gem5 and transfer the
state to KVM. Since we do not have a BIOS loaded, we pretend that the
INIT interrupt suspends the CPU after reset.

--HG--
extra : rebase_source : 7f3b25f3801d68f668b6cd91eaf50d6f48ee2a6a

src/arch/x86/interrupts.hh
src/cpu/kvm/base.cc
src/cpu/kvm/base.hh
src/cpu/kvm/x86_cpu.cc

index 8997d7402be36a3016b7a4616166368522c82aee..dabee544166801b9f9a2f7701edd7da2bb9addb7 100644 (file)
@@ -281,6 +281,12 @@ class Interrupts : public BasicPioDevice, IntDevice
      * @return true if there are interrupts pending.
      */
     bool checkInterruptsRaw() const;
+    /**
+     * Check if there are pending unmaskable interrupts.
+     *
+     * @return true there are unmaskable interrupts pending.
+     */
+    bool hasPendingUnmaskable() const { return pendingUnmaskableInt; }
     Fault getInterrupt(ThreadContext *tc);
     void updateIntrInfo(ThreadContext *tc);
 
index 26ffe37a54fc250e7535c31d69be0fdfeb911a34..1149a37043c0728a9e7cb4e5b4ef101edb4a4c4c 100644 (file)
@@ -543,6 +543,12 @@ BaseKvmCPU::tick()
               delay = kvmRun(ticksToExecute);
           }
 
+          // The CPU might have been suspended before entering into
+          // KVM. Assume that the CPU was suspended /before/ entering
+          // into KVM and skip the exit handling.
+          if (_status == Idle)
+              break;
+
           // Entering into KVM implies that we'll have to reload the thread
           // context from KVM if we want to access it. Flag the KVM state as
           // dirty with respect to the cached thread context.
index 5a0b80b15a67a682bebe4e66fcb3ce91f7e5011e..8191d9d92ee117a75ec3e22cfadbdaf4e0901699 100644 (file)
@@ -241,6 +241,11 @@ class BaseKvmCPU : public BaseCPU
      * make sure that the KVM state is synchronized and that the TC is
      * invalidated after entering KVM.
      *
+     * @note This method does not normally cause any state
+     * transitions. However, if it may suspend the CPU by suspending
+     * the thread, which leads to a transition to the Idle state. In
+     * such a case, kvm <i>must not</i> be entered.
+     *
      * @param ticks Number of ticks to execute, set to 0 to exit
      * immediately after finishing pending operations.
      * @return Number of ticks executed (see note)
index b79207fab304638b6d0908a59f34368ab87a703d..3313c8db06ec8e411ab5ae308fdd625165e5d6f5 100644 (file)
@@ -1137,7 +1137,27 @@ X86KvmCPU::deliverInterrupts()
     interrupts->updateIntrInfo(tc);
 
     X86Interrupt *x86int(dynamic_cast<X86Interrupt *>(fault.get()));
-    if (x86int) {
+    if (dynamic_cast<NonMaskableInterrupt *>(fault.get())) {
+        DPRINTF(KvmInt, "Delivering NMI\n");
+        kvmNonMaskableInterrupt();
+    } else if (dynamic_cast<InitInterrupt *>(fault.get())) {
+        DPRINTF(KvmInt, "INIT interrupt\n");
+        fault.get()->invoke(tc);
+        // Delay the kvm state update since we won't enter KVM on this
+        // tick.
+        threadContextDirty = true;
+        // HACK: gem5 doesn't actually have any BIOS code, which means
+        // that we need to halt the thread and wait for a startup
+        // interrupt before restarting the thread. The simulated CPUs
+        // use the same kind of hack using a microcode routine.
+        thread->suspend();
+    } else if (dynamic_cast<StartupInterrupt *>(fault.get())) {
+        DPRINTF(KvmInt, "STARTUP interrupt\n");
+        fault.get()->invoke(tc);
+        // The kvm state is assumed to have been updated when entering
+        // kvmRun(), so we need to update manually it here.
+        updateKvmState();
+    } else if (x86int) {
         struct kvm_interrupt kvm_int;
         kvm_int.irq = x86int->getVector();
 
@@ -1145,9 +1165,6 @@ X86KvmCPU::deliverInterrupts()
                 fault->name(), kvm_int.irq);
 
         kvmInterrupt(kvm_int);
-    } else if (dynamic_cast<NonMaskableInterrupt *>(fault.get())) {
-        DPRINTF(KvmInt, "Delivering NMI\n");
-        kvmNonMaskableInterrupt();
     } else {
         panic("KVM: Unknown interrupt type\n");
     }
@@ -1160,7 +1177,12 @@ X86KvmCPU::kvmRun(Tick ticks)
     struct kvm_run &kvm_run(*getKvmRunState());
 
     if (interrupts->checkInterruptsRaw()) {
-        if (kvm_run.ready_for_interrupt_injection) {
+        if (interrupts->hasPendingUnmaskable()) {
+            DPRINTF(KvmInt,
+                    "Delivering unmaskable interrupt.\n");
+            syncThreadContext();
+            deliverInterrupts();
+        } else if (kvm_run.ready_for_interrupt_injection) {
             // KVM claims that it is ready for an interrupt. It might
             // be lying if we just updated rflags and disabled
             // interrupts (e.g., by doing a CPU handover). Let's sync
@@ -1187,7 +1209,12 @@ X86KvmCPU::kvmRun(Tick ticks)
         kvm_run.request_interrupt_window = 0;
     }
 
-    return kvmRunWrapper(ticks);
+    // The CPU might have been suspended as a result of the INIT
+    // interrupt delivery hack. In that case, don't enter into KVM.
+    if (_status == Idle)
+        return 0;
+    else
+        return kvmRunWrapper(ticks);
 }
 
 Tick