cpu: implement a bi-mode branch predictor
[gem5.git] / src / cpu / kvm / base.cc
index 04e35854a4351e76ccfb7407985673160149ee8b..2082670dc03735ff1c8ee0b8654cf445cce57644 100644 (file)
 #include <csignal>
 #include <ostream>
 
+#include "arch/mmapped_ipr.hh"
 #include "arch/utility.hh"
 #include "cpu/kvm/base.hh"
+#include "debug/Checkpoint.hh"
+#include "debug/Drain.hh"
 #include "debug/Kvm.hh"
 #include "debug/KvmIO.hh"
 #include "debug/KvmRun.hh"
 #include "sim/process.hh"
 #include "sim/system.hh"
 
+#include <signal.h>
+
 /* Used by some KVM macros */
 #define PAGE_SIZE pageSize
 
-volatile bool timerOverflowed = false;
-
-static void
-onTimerOverflow(int signo, siginfo_t *si, void *data)
-{
-    timerOverflowed = true;
-}
-
 BaseKvmCPU::BaseKvmCPU(BaseKvmCPUParams *params)
     : BaseCPU(params),
       vm(*params->kvmVM),
       _status(Idle),
       dataPort(name() + ".dcache_port", this),
       instPort(name() + ".icache_port", this),
-      contextDirty(true),
+      threadContextDirty(true),
+      kvmStateDirty(false),
       vcpuID(vm.allocVCPUID()), vcpuFD(-1), vcpuMMapSize(0),
       _kvmRun(NULL), mmioRing(NULL),
       pageSize(sysconf(_SC_PAGE_SIZE)),
       tickEvent(*this),
-      hostFactor(params->hostFactor)
+      activeInstPeriod(0),
+      perfControlledByTimer(params->usePerfOverflow),
+      hostFactor(params->hostFactor),
+      drainManager(NULL),
+      ctrInsts(0)
 {
     if (pageSize == -1)
         panic("KVM: Failed to determine host page size (%i)\n",
@@ -88,13 +90,6 @@ BaseKvmCPU::BaseKvmCPU(BaseKvmCPUParams *params)
     thread->setStatus(ThreadContext::Halted);
     tc = thread->getTC();
     threadContexts.push_back(tc);
-
-    setupCounters();
-    setupSignalHandler();
-
-    runTimer.reset(new PosixKvmTimer(KVM_TIMER_SIGNAL, CLOCK_MONOTONIC,
-                                     params->hostFactor,
-                                     params->clock));
 }
 
 BaseKvmCPU::~BaseKvmCPU()
@@ -124,6 +119,9 @@ BaseKvmCPU::init()
 void
 BaseKvmCPU::startup()
 {
+    const BaseKvmCPUParams * const p(
+        dynamic_cast<const BaseKvmCPUParams *>(params()));
+
     Kvm &kvm(vm.kvm);
 
     BaseCPU::startup();
@@ -150,13 +148,52 @@ BaseKvmCPU::startup()
     // available. The offset into the KVM's communication page is
     // provided by the coalesced MMIO capability.
     int mmioOffset(kvm.capCoalescedMMIO());
-    if (mmioOffset) {
+    if (!p->useCoalescedMMIO) {
+        inform("KVM: Coalesced MMIO disabled by config.\n");
+    } else if (mmioOffset) {
         inform("KVM: Coalesced IO available\n");
         mmioRing = (struct kvm_coalesced_mmio_ring *)(
             (char *)_kvmRun + (mmioOffset * pageSize));
     } else {
         inform("KVM: Coalesced not supported by host OS\n");
     }
+
+    thread->startup();
+
+    Event *startupEvent(
+        new EventWrapper<BaseKvmCPU,
+                         &BaseKvmCPU::startupThread>(this, true));
+    schedule(startupEvent, curTick());
+}
+
+void
+BaseKvmCPU::startupThread()
+{
+    // Do thread-specific initialization. We need to setup signal
+    // delivery for counters and timers from within the thread that
+    // will execute the event queue to ensure that signals are
+    // delivered to the right threads.
+    const BaseKvmCPUParams * const p(
+        dynamic_cast<const BaseKvmCPUParams *>(params()));
+
+    vcpuThread = pthread_self();
+
+    // Setup signal handlers. This has to be done after the vCPU is
+    // created since it manipulates the vCPU signal mask.
+    setupSignalHandler();
+
+    setupCounters();
+
+    if (p->usePerfOverflow)
+        runTimer.reset(new PerfKvmTimer(hwCycles,
+                                        KVM_KICK_SIGNAL,
+                                        p->hostFactor,
+                                        p->hostFreq));
+    else
+        runTimer.reset(new PosixKvmTimer(KVM_KICK_SIGNAL, CLOCK_MONOTONIC,
+                                         p->hostFactor,
+                                         p->hostFreq));
+
 }
 
 void
@@ -166,11 +203,26 @@ BaseKvmCPU::regStats()
 
     BaseCPU::regStats();
 
+    numInsts
+        .name(name() + ".committedInsts")
+        .desc("Number of instructions committed")
+        ;
+
     numVMExits
         .name(name() + ".numVMExits")
         .desc("total number of KVM exits")
         ;
 
+    numVMHalfEntries
+        .name(name() + ".numVMHalfEntries")
+        .desc("number of KVM entries to finalize pending operations")
+        ;
+
+    numExitSignal
+        .name(name() + ".numExitSignal")
+        .desc("exits due to signal delivery")
+        ;
+
     numMMIO
         .name(name() + ".numMMIO")
         .desc("number of VM exits due to memory mapped IO")
@@ -205,6 +257,11 @@ BaseKvmCPU::regStats()
 void
 BaseKvmCPU::serializeThread(std::ostream &os, ThreadID tid)
 {
+    if (DTRACE(Checkpoint)) {
+        DPRINTF(Checkpoint, "KVM: Serializing thread %i:\n", tid);
+        dump();
+    }
+
     assert(tid == 0);
     assert(_status == Idle);
     thread->serialize(os);
@@ -214,10 +271,12 @@ void
 BaseKvmCPU::unserializeThread(Checkpoint *cp, const std::string &section,
                               ThreadID tid)
 {
+    DPRINTF(Checkpoint, "KVM: Unserialize thread %i:\n", tid);
+
     assert(tid == 0);
     assert(_status == Idle);
     thread->unserialize(cp, section);
-    contextDirty = true;
+    threadContextDirty = true;
 }
 
 unsigned int
@@ -226,15 +285,62 @@ BaseKvmCPU::drain(DrainManager *dm)
     if (switchedOut())
         return 0;
 
-    DPRINTF(Kvm, "drain\n");
+    DPRINTF(Drain, "BaseKvmCPU::drain\n");
+    switch (_status) {
+      case Running:
+        // The base KVM code is normally ready when it is in the
+        // Running state, but the architecture specific code might be
+        // of a different opinion. This may happen when the CPU been
+        // notified of an event that hasn't been accepted by the vCPU
+        // yet.
+        if (!archIsDrained()) {
+            drainManager = dm;
+            return 1;
+        }
+
+        // The state of the CPU is consistent, so we don't need to do
+        // anything special to drain it. We simply de-schedule the
+        // tick event and enter the Idle state to prevent nasty things
+        // like MMIOs from happening.
+        if (tickEvent.scheduled())
+            deschedule(tickEvent);
+        _status = Idle;
+
+        /** FALLTHROUGH */
+      case Idle:
+        // Idle, no need to drain
+        assert(!tickEvent.scheduled());
 
-    // De-schedule the tick event so we don't insert any more MMIOs
-    // into the system while it is draining.
-    if (tickEvent.scheduled())
-        deschedule(tickEvent);
+        // Sync the thread context here since we'll need it when we
+        // switch CPUs or checkpoint the CPU.
+        syncThreadContext();
 
-    _status = Idle;
-    return 0;
+        return 0;
+
+      case RunningServiceCompletion:
+        // The CPU has just requested a service that was handled in
+        // the RunningService state, but the results have still not
+        // been reported to the CPU. Now, we /could/ probably just
+        // update the register state ourselves instead of letting KVM
+        // handle it, but that would be tricky. Instead, we enter KVM
+        // and let it do its stuff.
+        drainManager = dm;
+
+        DPRINTF(Drain, "KVM CPU is waiting for service completion, "
+                "requesting drain.\n");
+        return 1;
+
+      case RunningService:
+        // We need to drain since the CPU is waiting for service (e.g., MMIOs)
+        drainManager = dm;
+
+        DPRINTF(Drain, "KVM CPU is waiting for service, requesting drain.\n");
+        return 1;
+
+      default:
+        panic("KVM: Unhandled CPU state in drain()\n");
+        return 0;
+    }
 }
 
 void
@@ -263,10 +369,10 @@ BaseKvmCPU::drainResume()
 void
 BaseKvmCPU::switchOut()
 {
-    BaseCPU::switchOut();
-
     DPRINTF(Kvm, "switchOut\n");
 
+    BaseCPU::switchOut();
+
     // We should have drained prior to executing a switchOut, which
     // means that the tick event shouldn't be scheduled and the CPU is
     // idle.
@@ -288,8 +394,12 @@ BaseKvmCPU::takeOverFrom(BaseCPU *cpu)
     assert(_status == Idle);
     assert(threadContexts.size() == 1);
 
-    // Force a gem5 -> KVM context synchronization
-    contextDirty = true;
+    // Force an update of the KVM state here instead of flagging the
+    // TC as dirty. This is not ideal from a performance point of
+    // view, but it makes debugging easier as it allows meaningful KVM
+    // state to be dumped before and after a takeover.
+    updateKvmState();
+    threadContextDirty = false;
 }
 
 void
@@ -305,6 +415,13 @@ void
 BaseKvmCPU::wakeup()
 {
     DPRINTF(Kvm, "wakeup()\n");
+    // This method might have been called from another
+    // context. Migrate to this SimObject's event queue when
+    // delivering the wakeup signal.
+    EventQueue::ScopedMigration migrate(eventQueue());
+
+    // Kick the vCPU to get it to come out of KVM.
+    kick();
 
     if (thread->status() != ThreadContext::Suspended)
         return;
@@ -323,8 +440,7 @@ BaseKvmCPU::activateContext(ThreadID thread_num, Cycles delay)
     assert(_status == Idle);
     assert(!tickEvent.scheduled());
 
-    numCycles += ticksToCycles(thread->lastActivate - thread->lastSuspend)
-        * hostFactor;
+    numCycles += ticksToCycles(thread->lastActivate - thread->lastSuspend);
 
     schedule(tickEvent, clockEdge(delay));
     _status = Running;
@@ -368,17 +484,26 @@ BaseKvmCPU::haltContext(ThreadID thread_num)
     suspendContext(thread_num);
 }
 
+ThreadContext *
+BaseKvmCPU::getContext(int tn)
+{
+    assert(tn == 0);
+    syncThreadContext();
+    return tc;
+}
+
+
 Counter
 BaseKvmCPU::totalInsts() const
 {
-    return hwInstructions.read();
+    return ctrInsts;
 }
 
 Counter
 BaseKvmCPU::totalOps() const
 {
     hack_once("Pretending totalOps is equivalent to totalInsts()\n");
-    return hwInstructions.read();
+    return ctrInsts;
 }
 
 void
@@ -390,77 +515,192 @@ BaseKvmCPU::dump()
 void
 BaseKvmCPU::tick()
 {
-    assert(_status == Running);
-
-    DPRINTF(KvmRun, "Entering KVM...\n");
-
-    if (contextDirty) {
-        contextDirty = false;
-        updateKvmState();
-    }
-
-    Tick ticksToExecute(mainEventQueue.nextTick() - curTick());
-    Tick ticksExecuted(kvmRun(ticksToExecute));
-    updateThreadContext();
-
-    Tick delay(ticksExecuted + handleKvmExit());
+    Tick delay(0);
+    assert(_status != Idle);
 
     switch (_status) {
-      case Running:
-        schedule(tickEvent, clockEdge(ticksToCycles(delay)));
+      case RunningService:
+        // handleKvmExit() will determine the next state of the CPU
+        delay = handleKvmExit();
+
+        if (tryDrain())
+            _status = Idle;
         break;
 
+      case RunningServiceCompletion:
+      case Running: {
+          EventQueue *q = curEventQueue();
+          Tick ticksToExecute(q->nextTick() - curTick());
+
+          // We might need to update the KVM state.
+          syncKvmState();
+
+          // Setup any pending instruction count breakpoints using
+          // PerfEvent.
+          setupInstStop();
+
+          DPRINTF(KvmRun, "Entering KVM...\n");
+          if (drainManager) {
+              // Force an immediate exit from KVM after completing
+              // pending operations. The architecture-specific code
+              // takes care to run until it is in a state where it can
+              // safely be drained.
+              delay = kvmRunDrain();
+          } else {
+              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.
+          kvmStateDirty = true;
+
+          // Enter into the RunningService state unless the
+          // simulation was stopped by a timer.
+          if (_kvmRun->exit_reason !=  KVM_EXIT_INTR) {
+              _status = RunningService;
+          } else {
+              ++numExitSignal;
+              _status = Running;
+          }
+
+          // Service any pending instruction events. The vCPU should
+          // have exited in time for the event using the instruction
+          // counter configured by setupInstStop().
+          comInstEventQueue[0]->serviceEvents(ctrInsts);
+          system->instEventQueue.serviceEvents(system->totalNumInsts);
+
+          if (tryDrain())
+              _status = Idle;
+      } break;
+
       default:
-        /* The CPU is halted or waiting for an interrupt from a
-         * device. Don't start it. */
-        break;
+        panic("BaseKvmCPU entered tick() in an illegal state (%i)\n",
+              _status);
     }
+
+    // Schedule a new tick if we are still running
+    if (_status != Idle)
+        schedule(tickEvent, clockEdge(ticksToCycles(delay)));
 }
 
 Tick
-BaseKvmCPU::kvmRun(Tick ticks)
+BaseKvmCPU::kvmRunDrain()
 {
-    uint64_t baseCycles(hwCycles.read());
-    uint64_t baseInstrs(hwInstructions.read());
+    // By default, the only thing we need to drain is a pending IO
+    // operation which assumes that we are in the
+    // RunningServiceCompletion state.
+    assert(_status == RunningServiceCompletion);
+
+    // Deliver the data from the pending IO operation and immediately
+    // exit.
+    return kvmRun(0);
+}
 
-    if (ticks < runTimer->resolution()) {
-        DPRINTF(KvmRun, "KVM: Adjusting tick count (%i -> %i)\n",
-                ticks, runTimer->resolution());
-        ticks = runTimer->resolution();
-    }
+uint64_t
+BaseKvmCPU::getHostCycles() const
+{
+    return hwCycles.read();
+}
 
+Tick
+BaseKvmCPU::kvmRun(Tick ticks)
+{
+    Tick ticksExecuted;
     DPRINTF(KvmRun, "KVM: Executing for %i ticks\n", ticks);
-    timerOverflowed = false;
-    runTimer->arm(ticks);
-    startCounters();
-    if (ioctl(KVM_RUN) == -1) {
-        if (errno != EINTR)
-            panic("KVM: Failed to start virtual CPU (errno: %i)\n",
-                  errno);
-    }
-    stopCounters();
-    runTimer->disarm();
-
-    uint64_t cyclesExecuted(hwCycles.read() - baseCycles);
-    Tick ticksExecuted(runTimer->ticksFromHostCycles(cyclesExecuted));
-
-    if (ticksExecuted < ticks &&
-        timerOverflowed &&
-        _kvmRun->exit_reason == KVM_EXIT_INTR) {
-        // TODO: We should probably do something clever here...
-        warn("KVM: Early timer event, requested %i ticks but got %i ticks.\n",
-             ticks, ticksExecuted);
+
+    if (ticks == 0) {
+        // Settings ticks == 0 is a special case which causes an entry
+        // into KVM that finishes pending operations (e.g., IO) and
+        // then immediately exits.
+        DPRINTF(KvmRun, "KVM: Delivering IO without full guest entry\n");
+
+        ++numVMHalfEntries;
+
+        // Send a KVM_KICK_SIGNAL to the vCPU thread (i.e., this
+        // thread). The KVM control signal is masked while executing
+        // in gem5 and gets unmasked temporarily as when entering
+        // KVM. See setSignalMask() and setupSignalHandler().
+        kick();
+
+        // Start the vCPU. KVM will check for signals after completing
+        // pending operations (IO). Since the KVM_KICK_SIGNAL is
+        // pending, this forces an immediate exit to gem5 again. We
+        // don't bother to setup timers since this shouldn't actually
+        // execute any code (other than completing half-executed IO
+        // instructions) in the guest.
+        ioctlRun();
+
+        // We always execute at least one cycle to prevent the
+        // BaseKvmCPU::tick() to be rescheduled on the same tick
+        // twice.
+        ticksExecuted = clockPeriod();
+    } else {
+        // This method is executed as a result of a tick event. That
+        // means that the event queue will be locked when entering the
+        // method. We temporarily unlock the event queue to allow
+        // other threads to steal control of this thread to inject
+        // interrupts. They will typically lock the queue and then
+        // force an exit from KVM by kicking the vCPU.
+        EventQueue::ScopedRelease release(curEventQueue());
+
+        if (ticks < runTimer->resolution()) {
+            DPRINTF(KvmRun, "KVM: Adjusting tick count (%i -> %i)\n",
+                    ticks, runTimer->resolution());
+            ticks = runTimer->resolution();
+        }
+
+        // Get hardware statistics after synchronizing contexts. The KVM
+        // state update might affect guest cycle counters.
+        uint64_t baseCycles(getHostCycles());
+        uint64_t baseInstrs(hwInstructions.read());
+
+        // Arm the run timer and start the cycle timer if it isn't
+        // controlled by the overflow timer. Starting/stopping the cycle
+        // timer automatically starts the other perf timers as they are in
+        // the same counter group.
+        runTimer->arm(ticks);
+        if (!perfControlledByTimer)
+            hwCycles.start();
+
+        ioctlRun();
+
+        runTimer->disarm();
+        if (!perfControlledByTimer)
+            hwCycles.stop();
+
+        // The control signal may have been delivered after we exited
+        // from KVM. It will be pending in that case since it is
+        // masked when we aren't executing in KVM. Discard it to make
+        // sure we don't deliver it immediately next time we try to
+        // enter into KVM.
+        discardPendingSignal(KVM_KICK_SIGNAL);
+
+        const uint64_t hostCyclesExecuted(getHostCycles() - baseCycles);
+        const uint64_t simCyclesExecuted(hostCyclesExecuted * hostFactor);
+        const uint64_t instsExecuted(hwInstructions.read() - baseInstrs);
+        ticksExecuted = runTimer->ticksFromHostCycles(hostCyclesExecuted);
+
+        /* Update statistics */
+        numCycles += simCyclesExecuted;;
+        numInsts += instsExecuted;
+        ctrInsts += instsExecuted;
+        system->totalNumInsts += instsExecuted;
+
+        DPRINTF(KvmRun,
+                "KVM: Executed %i instructions in %i cycles "
+                "(%i ticks, sim cycles: %i).\n",
+                instsExecuted, hostCyclesExecuted, ticksExecuted, simCyclesExecuted);
     }
 
-    numCycles += cyclesExecuted * hostFactor;
     ++numVMExits;
 
-    DPRINTF(KvmRun, "KVM: Executed %i instructions in %i cycles (%i ticks, sim cycles: %i).\n",
-            hwInstructions.read() - baseInstrs,
-            cyclesExecuted,
-            ticksExecuted,
-            cyclesExecuted * hostFactor);
-
     return ticksExecuted + flushCoalescedMMIO();
 }
 
@@ -604,11 +844,39 @@ BaseKvmCPU::getAndFormatOneReg(uint64_t id) const
 #endif
 }
 
+void
+BaseKvmCPU::syncThreadContext()
+{
+    if (!kvmStateDirty)
+        return;
+
+    assert(!threadContextDirty);
+
+    updateThreadContext();
+    kvmStateDirty = false;
+}
+
+void
+BaseKvmCPU::syncKvmState()
+{
+    if (!threadContextDirty)
+        return;
+
+    assert(!kvmStateDirty);
+
+    updateKvmState();
+    threadContextDirty = false;
+}
+
 Tick
 BaseKvmCPU::handleKvmExit()
 {
     DPRINTF(KvmRun, "handleKvmExit (exit_reason: %i)\n", _kvmRun->exit_reason);
+    assert(_status == RunningService);
 
+    // Switch into the running state by default. Individual handlers
+    // can override this.
+    _status = Running;
     switch (_kvmRun->exit_reason) {
       case KVM_EXIT_UNKNOWN:
         return handleKvmExitUnknown();
@@ -617,6 +885,7 @@ BaseKvmCPU::handleKvmExit()
         return handleKvmExitException();
 
       case KVM_EXIT_IO:
+        _status = RunningServiceCompletion;
         ++numIO;
         return handleKvmExitIO();
 
@@ -636,6 +905,7 @@ BaseKvmCPU::handleKvmExit()
         return 0;
 
       case KVM_EXIT_MMIO:
+        _status = RunningServiceCompletion;
         /* Service memory mapped IO requests */
         DPRINTF(KvmIO, "KVM: Handling MMIO (w: %u, addr: 0x%x, len: %u)\n",
                 _kvmRun->mmio.is_write,
@@ -661,6 +931,7 @@ BaseKvmCPU::handleKvmExit()
               _kvmRun->internal.suberror);
 
       default:
+        dump();
         panic("KVM: Unexpected exit (exit_reason: %u)\n", _kvmRun->exit_reason);
     }
 }
@@ -690,6 +961,7 @@ BaseKvmCPU::handleKvmExitIRQWindowOpen()
 Tick
 BaseKvmCPU::handleKvmExitUnknown()
 {
+    dump();
     panic("KVM: Unknown error when starting vCPU (hw reason: 0x%llx)\n",
           _kvmRun->hw.hardware_exit_reason);
 }
@@ -697,6 +969,7 @@ BaseKvmCPU::handleKvmExitUnknown()
 Tick
 BaseKvmCPU::handleKvmExitException()
 {
+    dump();
     panic("KVM: Got exception when starting vCPU "
           "(exception: %u, error_code: %u)\n",
           _kvmRun->ex.exception, _kvmRun->ex.error_code);
@@ -705,6 +978,7 @@ BaseKvmCPU::handleKvmExitException()
 Tick
 BaseKvmCPU::handleKvmExitFailEntry()
 {
+    dump();
     panic("KVM: Failed to enter virtualized mode (hw reason: 0x%llx)\n",
           _kvmRun->fail_entry.hardware_entry_failure_reason);
 }
@@ -712,14 +986,61 @@ BaseKvmCPU::handleKvmExitFailEntry()
 Tick
 BaseKvmCPU::doMMIOAccess(Addr paddr, void *data, int size, bool write)
 {
-    mmio_req.setPhys(paddr, size,
-                     0, /* flags */
-                     dataMasterId());
+    ThreadContext *tc(thread->getTC());
+    syncThreadContext();
+
+    mmio_req.setPhys(paddr, size, Request::UNCACHEABLE, dataMasterId());
+    // Some architectures do need to massage physical addresses a bit
+    // before they are inserted into the memory system. This enables
+    // APIC accesses on x86 and m5ops where supported through a MMIO
+    // interface.
+    BaseTLB::Mode tlb_mode(write ? BaseTLB::Write : BaseTLB::Read);
+    Fault fault(tc->getDTBPtr()->finalizePhysical(&mmio_req, tc, tlb_mode));
+    if (fault != NoFault)
+        warn("Finalization of MMIO address failed: %s\n", fault->name());
+
 
     const MemCmd cmd(write ? MemCmd::WriteReq : MemCmd::ReadReq);
     Packet pkt(&mmio_req, cmd);
     pkt.dataStatic(data);
-    return dataPort.sendAtomic(&pkt);
+
+    if (mmio_req.isMmappedIpr()) {
+        // We currently assume that there is no need to migrate to a
+        // different event queue when doing IPRs. Currently, IPRs are
+        // only used for m5ops, so it should be a valid assumption.
+        const Cycles ipr_delay(write ?
+                             TheISA::handleIprWrite(tc, &pkt) :
+                             TheISA::handleIprRead(tc, &pkt));
+        return clockPeriod() * ipr_delay;
+    } else {
+        // Temporarily lock and migrate to the event queue of the
+        // VM. This queue is assumed to "own" all devices we need to
+        // access if running in multi-core mode.
+        EventQueue::ScopedMigration migrate(vm.eventQueue());
+
+        return dataPort.sendAtomic(&pkt);
+    }
+}
+
+void
+BaseKvmCPU::setSignalMask(const sigset_t *mask)
+{
+    std::unique_ptr<struct kvm_signal_mask> kvm_mask;
+
+    if (mask) {
+        kvm_mask.reset((struct kvm_signal_mask *)operator new(
+                           sizeof(struct kvm_signal_mask) + sizeof(*mask)));
+        // The kernel and the user-space headers have different ideas
+        // about the size of sigset_t. This seems like a massive hack,
+        // but is actually what qemu does.
+        assert(sizeof(*mask) >= 8);
+        kvm_mask->len = 8;
+        memcpy(kvm_mask->sigset, mask, kvm_mask->len);
+    }
+
+    if (ioctl(KVM_SET_SIGNAL_MASK, (void *)kvm_mask.get()) == -1)
+        panic("KVM: Failed to set vCPU signal mask (errno: %i)\n",
+              errno);
 }
 
 int
@@ -758,16 +1079,76 @@ BaseKvmCPU::flushCoalescedMMIO()
     return ticks;
 }
 
+/**
+ * Dummy handler for KVM kick signals.
+ *
+ * @note This function is usually not called since the kernel doesn't
+ * seem to deliver signals when the signal is only unmasked when
+ * running in KVM. This doesn't matter though since we are only
+ * interested in getting KVM to exit, which happens as expected. See
+ * setupSignalHandler() and kvmRun() for details about KVM signal
+ * handling.
+ */
+static void
+onKickSignal(int signo, siginfo_t *si, void *data)
+{
+}
+
 void
 BaseKvmCPU::setupSignalHandler()
 {
     struct sigaction sa;
 
     memset(&sa, 0, sizeof(sa));
-    sa.sa_sigaction = onTimerOverflow;
+    sa.sa_sigaction = onKickSignal;
     sa.sa_flags = SA_SIGINFO | SA_RESTART;
-    if (sigaction(KVM_TIMER_SIGNAL, &sa, NULL) == -1)
-        panic("KVM: Failed to setup vCPU signal handler\n");
+    if (sigaction(KVM_KICK_SIGNAL, &sa, NULL) == -1)
+        panic("KVM: Failed to setup vCPU timer signal handler\n");
+
+    sigset_t sigset;
+    if (pthread_sigmask(SIG_BLOCK, NULL, &sigset) == -1)
+        panic("KVM: Failed get signal mask\n");
+
+    // Request KVM to setup the same signal mask as we're currently
+    // running with except for the KVM control signal. We'll sometimes
+    // need to raise the KVM_KICK_SIGNAL to cause immediate exits from
+    // KVM after servicing IO requests. See kvmRun().
+    sigdelset(&sigset, KVM_KICK_SIGNAL);
+    setSignalMask(&sigset);
+
+    // Mask our control signals so they aren't delivered unless we're
+    // actually executing inside KVM.
+    sigaddset(&sigset, KVM_KICK_SIGNAL);
+    if (pthread_sigmask(SIG_SETMASK, &sigset, NULL) == -1)
+        panic("KVM: Failed mask the KVM control signals\n");
+}
+
+bool
+BaseKvmCPU::discardPendingSignal(int signum) const
+{
+    int discardedSignal;
+
+    // Setting the timeout to zero causes sigtimedwait to return
+    // immediately.
+    struct timespec timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_nsec = 0;
+
+    sigset_t sigset;
+    sigemptyset(&sigset);
+    sigaddset(&sigset, signum);
+
+    do {
+        discardedSignal = sigtimedwait(&sigset, NULL, &timeout);
+    } while (discardedSignal == -1 && errno == EINTR);
+
+    if (discardedSignal == signum)
+        return true;
+    else if (discardedSignal == -1 && errno == EAGAIN)
+        return false;
+    else
+        panic("Unexpected return value from sigtimedwait: %i (errno: %i)\n",
+              discardedSignal, errno);
 }
 
 void
@@ -778,28 +1159,109 @@ BaseKvmCPU::setupCounters()
                                 PERF_COUNT_HW_CPU_CYCLES);
     cfgCycles.disabled(true)
         .pinned(true);
+
+    // Try to exclude the host. We set both exclude_hv and
+    // exclude_host since different architectures use slightly
+    // different APIs in the kernel.
+    cfgCycles.exclude_hv(true)
+        .exclude_host(true);
+
+    if (perfControlledByTimer) {
+        // We need to configure the cycles counter to send overflows
+        // since we are going to use it to trigger timer signals that
+        // trap back into m5 from KVM. In practice, this means that we
+        // need to set some non-zero sample period that gets
+        // overridden when the timer is armed.
+        cfgCycles.wakeupEvents(1)
+            .samplePeriod(42);
+    }
+
     hwCycles.attach(cfgCycles,
                     0); // TID (0 => currentThread)
 
-    DPRINTF(Kvm, "Attaching instruction counter...\n");
-    PerfKvmCounterConfig cfgInstructions(PERF_TYPE_HARDWARE,
-                                      PERF_COUNT_HW_INSTRUCTIONS);
-    hwInstructions.attach(cfgInstructions,
-                          0, // TID (0 => currentThread)
-                          hwCycles);
+    setupInstCounter();
+}
+
+bool
+BaseKvmCPU::tryDrain()
+{
+    if (!drainManager)
+        return false;
+
+    if (!archIsDrained()) {
+        DPRINTF(Drain, "tryDrain: Architecture code is not ready.\n");
+        return false;
+    }
+
+    if (_status == Idle || _status == Running) {
+        DPRINTF(Drain,
+                "tryDrain: CPU transitioned into the Idle state, drain done\n");
+        drainManager->signalDrainDone();
+        drainManager = NULL;
+        return true;
+    } else {
+        DPRINTF(Drain, "tryDrain: CPU not ready.\n");
+        return false;
+    }
 }
 
 void
-BaseKvmCPU::startCounters()
+BaseKvmCPU::ioctlRun()
 {
-    // We only need to start/stop the hwCycles counter since hwCycles
-    // and hwInstructions are a counter group with hwCycles as the
-    // group leader.
-    hwCycles.start();
+    if (ioctl(KVM_RUN) == -1) {
+        if (errno != EINTR)
+            panic("KVM: Failed to start virtual CPU (errno: %i)\n",
+                  errno);
+    }
+}
+
+void
+BaseKvmCPU::setupInstStop()
+{
+    if (comInstEventQueue[0]->empty()) {
+        setupInstCounter(0);
+    } else {
+        const uint64_t next(comInstEventQueue[0]->nextTick());
+
+        assert(next > ctrInsts);
+        setupInstCounter(next - ctrInsts);
+    }
 }
 
 void
-BaseKvmCPU::stopCounters()
+BaseKvmCPU::setupInstCounter(uint64_t period)
 {
-    hwCycles.stop();
+    // No need to do anything if we aren't attaching for the first
+    // time or the period isn't changing.
+    if (period == activeInstPeriod && hwInstructions.attached())
+        return;
+
+    PerfKvmCounterConfig cfgInstructions(PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_INSTRUCTIONS);
+
+    // Try to exclude the host. We set both exclude_hv and
+    // exclude_host since different architectures use slightly
+    // different APIs in the kernel.
+    cfgInstructions.exclude_hv(true)
+        .exclude_host(true);
+
+    if (period) {
+        // Setup a sampling counter if that has been requested.
+        cfgInstructions.wakeupEvents(1)
+            .samplePeriod(period);
+    }
+
+    // We need to detach and re-attach the counter to reliably change
+    // sampling settings. See PerfKvmCounter::period() for details.
+    if (hwInstructions.attached())
+        hwInstructions.detach();
+    assert(hwCycles.attached());
+    hwInstructions.attach(cfgInstructions,
+                          0, // TID (0 => currentThread)
+                          hwCycles);
+
+    if (period)
+        hwInstructions.enableSignals(KVM_KICK_SIGNAL);
+
+    activeInstPeriod = period;
 }