diff options
-rwxr-xr-x | SConstruct | 30 | ||||
-rw-r--r-- | src/cpu/kvm/BaseKvmCPU.py | 72 | ||||
-rw-r--r-- | src/cpu/kvm/KvmVM.py | 49 | ||||
-rw-r--r-- | src/cpu/kvm/SConscript | 60 | ||||
-rw-r--r-- | src/cpu/kvm/base.cc | 805 | ||||
-rw-r--r-- | src/cpu/kvm/base.hh | 489 | ||||
-rw-r--r-- | src/cpu/kvm/perfevent.cc | 246 | ||||
-rw-r--r-- | src/cpu/kvm/perfevent.hh | 347 | ||||
-rw-r--r-- | src/cpu/kvm/timer.cc | 112 | ||||
-rw-r--r-- | src/cpu/kvm/timer.hh | 206 | ||||
-rw-r--r-- | src/cpu/kvm/vm.cc | 370 | ||||
-rw-r--r-- | src/cpu/kvm/vm.hh | 390 |
12 files changed, 3176 insertions, 0 deletions
diff --git a/SConstruct b/SConstruct index 6354bf0ca..92440d7b9 100755 --- a/SConstruct +++ b/SConstruct @@ -944,6 +944,26 @@ if not have_fenv: print "Warning: Header file <fenv.h> not found." print " This host has no IEEE FP rounding mode control." +# Check if we should enable KVM-based hardware virtualization +have_kvm = conf.CheckHeader('linux/kvm.h', '<>') +if not have_kvm: + print "Info: Header file <linux/kvm.h> not found, " \ + "disabling KVM support." + +# Check if the requested target ISA is compatible with the host +def is_isa_kvm_compatible(isa): + isa_comp_table = { + } + try: + import platform + host_isa = platform.machine() + except: + print "Warning: Failed to determine host ISA." + return False + + return host_isa in isa_comp_table.get(isa, []) + + ###################################################################### # # Finish the configuration @@ -1038,6 +1058,7 @@ sticky_vars.AddVariables( BoolVariable('USE_POSIX_CLOCK', 'Use POSIX Clocks', have_posix_clock), BoolVariable('USE_FENV', 'Use <fenv.h> IEEE mode control', have_fenv), BoolVariable('CP_ANNOTATE', 'Enable critical path annotation capability', False), + BoolVariable('USE_KVM', 'Enable hardware virtualized (KVM) CPU models', have_kvm), EnumVariable('PROTOCOL', 'Coherence protocol for Ruby', 'None', all_protocols), ) @@ -1205,6 +1226,15 @@ for variant_path in variant_paths: if env['EFENCE']: env.Append(LIBS=['efence']) + if env['USE_KVM']: + if not have_kvm: + print "Warning: Can not enable KVM, host seems to lack KVM support" + env['USE_KVM'] = False + elif not is_isa_kvm_compatible(env['TARGET_ISA']): + print "Info: KVM support disabled due to unsupported host and " \ + "target ISA combination" + env['USE_KVM'] = False + # Save sticky variable settings back to current variables file sticky_vars.Save(current_vars_file, env) diff --git a/src/cpu/kvm/BaseKvmCPU.py b/src/cpu/kvm/BaseKvmCPU.py new file mode 100644 index 000000000..aa7ad4c2c --- /dev/null +++ b/src/cpu/kvm/BaseKvmCPU.py @@ -0,0 +1,72 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andreas Sandberg + +from m5.params import * +from m5.proxy import * + +from BaseCPU import BaseCPU +from KvmVM import KvmVM + +class BaseKvmCPU(BaseCPU): + type = 'BaseKvmCPU' + cxx_header = "cpu/kvm/base.hh" + abstract = True + + @classmethod + def export_method_cxx_predecls(cls, code): + code('#include "cpu/kvm/base.hh"') + + @classmethod + def export_methods(cls, code): + code(''' + void dump(); +''') + + @classmethod + def memory_mode(cls): + return 'atomic_noncaching' + + @classmethod + def require_caches(cls): + return False + + @classmethod + def support_take_over(cls): + return True + + kvmVM = Param.KvmVM(Parent.any, 'KVM VM (i.e., shared memory domain)') + hostFactor = Param.Float(1.0, "Cycle scale factor") diff --git a/src/cpu/kvm/KvmVM.py b/src/cpu/kvm/KvmVM.py new file mode 100644 index 000000000..478a91682 --- /dev/null +++ b/src/cpu/kvm/KvmVM.py @@ -0,0 +1,49 @@ +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andreas Sandberg + +from m5.params import * +from m5.proxy import * + +from m5.SimObject import SimObject + +class KvmVM(SimObject): + type = 'KvmVM' + cxx_header = "cpu/kvm/vm.hh" + + system = Param.System(Parent.any, "system object") + + coalescedMMIO = VectorParam.AddrRange([], "memory ranges for coalesced MMIO") diff --git a/src/cpu/kvm/SConscript b/src/cpu/kvm/SConscript new file mode 100644 index 000000000..a567720fa --- /dev/null +++ b/src/cpu/kvm/SConscript @@ -0,0 +1,60 @@ +# -*- mode:python -*- + +# Copyright (c) 2012 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andreas Sandberg + +Import('*') + +if env['USE_KVM']: + SimObject('KvmVM.py') + SimObject('BaseKvmCPU.py') + + Source('base.cc') + Source('vm.cc') + Source('perfevent.cc') + Source('timer.cc') + + DebugFlag('Kvm', 'Basic KVM Functionality') + DebugFlag('KvmContext', 'KVM/gem5 context synchronization') + DebugFlag('KvmIO', 'KVM MMIO diagnostics') + DebugFlag('KvmInt', 'KVM Interrupt handling') + DebugFlag('KvmRun', 'KvmRun entry/exit diagnostics') + DebugFlag('KvmTimer', 'KVM timing') + + CompoundFlag('KvmAll', [ 'Kvm', 'KvmContext', 'KvmRun', + 'KvmIO', 'KvmInt', 'KvmTimer' ], + 'All KVM debug flags') diff --git a/src/cpu/kvm/base.cc b/src/cpu/kvm/base.cc new file mode 100644 index 000000000..04e35854a --- /dev/null +++ b/src/cpu/kvm/base.cc @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#include <linux/kvm.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <cerrno> +#include <csignal> +#include <ostream> + +#include "arch/utility.hh" +#include "cpu/kvm/base.hh" +#include "debug/Kvm.hh" +#include "debug/KvmIO.hh" +#include "debug/KvmRun.hh" +#include "params/BaseKvmCPU.hh" +#include "sim/process.hh" +#include "sim/system.hh" + +/* 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), + vcpuID(vm.allocVCPUID()), vcpuFD(-1), vcpuMMapSize(0), + _kvmRun(NULL), mmioRing(NULL), + pageSize(sysconf(_SC_PAGE_SIZE)), + tickEvent(*this), + hostFactor(params->hostFactor) +{ + if (pageSize == -1) + panic("KVM: Failed to determine host page size (%i)\n", + errno); + + thread = new SimpleThread(this, 0, params->system, + params->itb, params->dtb, params->isa[0]); + 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() +{ + if (_kvmRun) + munmap(_kvmRun, vcpuMMapSize); + close(vcpuFD); +} + +void +BaseKvmCPU::init() +{ + BaseCPU::init(); + + if (numThreads != 1) + fatal("KVM: Multithreading not supported"); + + tc->initMemProxies(tc); + + // initialize CPU, including PC + if (FullSystem && !switchedOut()) + TheISA::initCPU(tc, tc->contextId()); + + mmio_req.setThreadContext(tc->contextId(), 0); +} + +void +BaseKvmCPU::startup() +{ + Kvm &kvm(vm.kvm); + + BaseCPU::startup(); + + assert(vcpuFD == -1); + + // Tell the VM that a CPU is about to start. + vm.cpuStartup(); + + // We can't initialize KVM CPUs in BaseKvmCPU::init() since we are + // not guaranteed that the parent KVM VM has initialized at that + // point. Initialize virtual CPUs here instead. + vcpuFD = vm.createVCPU(vcpuID); + + // Map the KVM run structure */ + vcpuMMapSize = kvm.getVCPUMMapSize(); + _kvmRun = (struct kvm_run *)mmap(0, vcpuMMapSize, + PROT_READ | PROT_WRITE, MAP_SHARED, + vcpuFD, 0); + if (_kvmRun == MAP_FAILED) + panic("KVM: Failed to map run data structure\n"); + + // Setup a pointer to the MMIO ring buffer if coalesced MMIO is + // available. The offset into the KVM's communication page is + // provided by the coalesced MMIO capability. + int mmioOffset(kvm.capCoalescedMMIO()); + 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"); + } +} + +void +BaseKvmCPU::regStats() +{ + using namespace Stats; + + BaseCPU::regStats(); + + numVMExits + .name(name() + ".numVMExits") + .desc("total number of KVM exits") + ; + + numMMIO + .name(name() + ".numMMIO") + .desc("number of VM exits due to memory mapped IO") + ; + + numCoalescedMMIO + .name(name() + ".numCoalescedMMIO") + .desc("number of coalesced memory mapped IO requests") + ; + + numIO + .name(name() + ".numIO") + .desc("number of VM exits due to legacy IO") + ; + + numHalt + .name(name() + ".numHalt") + .desc("number of VM exits due to wait for interrupt instructions") + ; + + numInterrupts + .name(name() + ".numInterrupts") + .desc("number of interrupts delivered") + ; + + numHypercalls + .name(name() + ".numHypercalls") + .desc("number of hypercalls") + ; +} + +void +BaseKvmCPU::serializeThread(std::ostream &os, ThreadID tid) +{ + assert(tid == 0); + assert(_status == Idle); + thread->serialize(os); +} + +void +BaseKvmCPU::unserializeThread(Checkpoint *cp, const std::string §ion, + ThreadID tid) +{ + assert(tid == 0); + assert(_status == Idle); + thread->unserialize(cp, section); + contextDirty = true; +} + +unsigned int +BaseKvmCPU::drain(DrainManager *dm) +{ + if (switchedOut()) + return 0; + + DPRINTF(Kvm, "drain\n"); + + // 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); + + _status = Idle; + return 0; +} + +void +BaseKvmCPU::drainResume() +{ + assert(!tickEvent.scheduled()); + + // We might have been switched out. In that case, we don't need to + // do anything. + if (switchedOut()) + return; + + DPRINTF(Kvm, "drainResume\n"); + verifyMemoryMode(); + + // The tick event is de-scheduled as a part of the draining + // process. Re-schedule it if the thread context is active. + if (tc->status() == ThreadContext::Active) { + schedule(tickEvent, nextCycle()); + _status = Running; + } else { + _status = Idle; + } +} + +void +BaseKvmCPU::switchOut() +{ + BaseCPU::switchOut(); + + DPRINTF(Kvm, "switchOut\n"); + + // We should have drained prior to executing a switchOut, which + // means that the tick event shouldn't be scheduled and the CPU is + // idle. + assert(!tickEvent.scheduled()); + assert(_status == Idle); +} + +void +BaseKvmCPU::takeOverFrom(BaseCPU *cpu) +{ + DPRINTF(Kvm, "takeOverFrom\n"); + + BaseCPU::takeOverFrom(cpu); + + // We should have drained prior to executing a switchOut, which + // means that the tick event shouldn't be scheduled and the CPU is + // idle. + assert(!tickEvent.scheduled()); + assert(_status == Idle); + assert(threadContexts.size() == 1); + + // Force a gem5 -> KVM context synchronization + contextDirty = true; +} + +void +BaseKvmCPU::verifyMemoryMode() const +{ + if (!(system->isAtomicMode() && system->bypassCaches())) { + fatal("The KVM-based CPUs requires the memory system to be in the " + "'atomic_noncaching' mode.\n"); + } +} + +void +BaseKvmCPU::wakeup() +{ + DPRINTF(Kvm, "wakeup()\n"); + + if (thread->status() != ThreadContext::Suspended) + return; + + thread->activate(); +} + +void +BaseKvmCPU::activateContext(ThreadID thread_num, Cycles delay) +{ + DPRINTF(Kvm, "ActivateContext %d (%d cycles)\n", thread_num, delay); + + assert(thread_num == 0); + assert(thread); + + assert(_status == Idle); + assert(!tickEvent.scheduled()); + + numCycles += ticksToCycles(thread->lastActivate - thread->lastSuspend) + * hostFactor; + + schedule(tickEvent, clockEdge(delay)); + _status = Running; +} + + +void +BaseKvmCPU::suspendContext(ThreadID thread_num) +{ + DPRINTF(Kvm, "SuspendContext %d\n", thread_num); + + assert(thread_num == 0); + assert(thread); + + if (_status == Idle) + return; + + assert(_status == Running); + + // The tick event may no be scheduled if the quest has requested + // the monitor to wait for interrupts. The normal CPU models can + // get their tick events descheduled by quiesce instructions, but + // that can't happen here. + if (tickEvent.scheduled()) + deschedule(tickEvent); + + _status = Idle; +} + +void +BaseKvmCPU::deallocateContext(ThreadID thread_num) +{ + // for now, these are equivalent + suspendContext(thread_num); +} + +void +BaseKvmCPU::haltContext(ThreadID thread_num) +{ + // for now, these are equivalent + suspendContext(thread_num); +} + +Counter +BaseKvmCPU::totalInsts() const +{ + return hwInstructions.read(); +} + +Counter +BaseKvmCPU::totalOps() const +{ + hack_once("Pretending totalOps is equivalent to totalInsts()\n"); + return hwInstructions.read(); +} + +void +BaseKvmCPU::dump() +{ + inform("State dumping not implemented."); +} + +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()); + + switch (_status) { + case Running: + schedule(tickEvent, clockEdge(ticksToCycles(delay))); + break; + + default: + /* The CPU is halted or waiting for an interrupt from a + * device. Don't start it. */ + break; + } +} + +Tick +BaseKvmCPU::kvmRun(Tick ticks) +{ + uint64_t baseCycles(hwCycles.read()); + uint64_t baseInstrs(hwInstructions.read()); + + if (ticks < runTimer->resolution()) { + DPRINTF(KvmRun, "KVM: Adjusting tick count (%i -> %i)\n", + ticks, runTimer->resolution()); + ticks = runTimer->resolution(); + } + + 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); + } + + 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(); +} + +void +BaseKvmCPU::kvmNonMaskableInterrupt() +{ + ++numInterrupts; + if (ioctl(KVM_NMI) == -1) + panic("KVM: Failed to deliver NMI to virtual CPU\n"); +} + +void +BaseKvmCPU::kvmInterrupt(const struct kvm_interrupt &interrupt) +{ + ++numInterrupts; + if (ioctl(KVM_INTERRUPT, (void *)&interrupt) == -1) + panic("KVM: Failed to deliver interrupt to virtual CPU\n"); +} + +void +BaseKvmCPU::getRegisters(struct kvm_regs ®s) const +{ + if (ioctl(KVM_GET_REGS, ®s) == -1) + panic("KVM: Failed to get guest registers\n"); +} + +void +BaseKvmCPU::setRegisters(const struct kvm_regs ®s) +{ + if (ioctl(KVM_SET_REGS, (void *)®s) == -1) + panic("KVM: Failed to set guest registers\n"); +} + +void +BaseKvmCPU::getSpecialRegisters(struct kvm_sregs ®s) const +{ + if (ioctl(KVM_GET_SREGS, ®s) == -1) + panic("KVM: Failed to get guest special registers\n"); +} + +void +BaseKvmCPU::setSpecialRegisters(const struct kvm_sregs ®s) +{ + if (ioctl(KVM_SET_SREGS, (void *)®s) == -1) + panic("KVM: Failed to set guest special registers\n"); +} + +void +BaseKvmCPU::getFPUState(struct kvm_fpu &state) const +{ + if (ioctl(KVM_GET_FPU, &state) == -1) + panic("KVM: Failed to get guest FPU state\n"); +} + +void +BaseKvmCPU::setFPUState(const struct kvm_fpu &state) +{ + if (ioctl(KVM_SET_FPU, (void *)&state) == -1) + panic("KVM: Failed to set guest FPU state\n"); +} + + +void +BaseKvmCPU::setOneReg(uint64_t id, const void *addr) +{ +#ifdef KVM_SET_ONE_REG + struct kvm_one_reg reg; + reg.id = id; + reg.addr = (uint64_t)addr; + + if (ioctl(KVM_SET_ONE_REG, ®) == -1) { + panic("KVM: Failed to set register (0x%x) value (errno: %i)\n", + id, errno); + } +#else + panic("KVM_SET_ONE_REG is unsupported on this platform.\n"); +#endif +} + +void +BaseKvmCPU::getOneReg(uint64_t id, void *addr) const +{ +#ifdef KVM_GET_ONE_REG + struct kvm_one_reg reg; + reg.id = id; + reg.addr = (uint64_t)addr; + + if (ioctl(KVM_GET_ONE_REG, ®) == -1) { + panic("KVM: Failed to get register (0x%x) value (errno: %i)\n", + id, errno); + } +#else + panic("KVM_GET_ONE_REG is unsupported on this platform.\n"); +#endif +} + +std::string +BaseKvmCPU::getAndFormatOneReg(uint64_t id) const +{ +#ifdef KVM_GET_ONE_REG + std::ostringstream ss; + + ss.setf(std::ios::hex, std::ios::basefield); + ss.setf(std::ios::showbase); +#define HANDLE_INTTYPE(len) \ + case KVM_REG_SIZE_U ## len: { \ + uint ## len ## _t value; \ + getOneReg(id, &value); \ + ss << value; \ + } break + +#define HANDLE_ARRAY(len) \ + case KVM_REG_SIZE_U ## len: { \ + uint8_t value[len / 8]; \ + getOneReg(id, value); \ + ss << "[" << value[0]; \ + for (int i = 1; i < len / 8; ++i) \ + ss << ", " << value[i]; \ + ss << "]"; \ + } break + + switch (id & KVM_REG_SIZE_MASK) { + HANDLE_INTTYPE(8); + HANDLE_INTTYPE(16); + HANDLE_INTTYPE(32); + HANDLE_INTTYPE(64); + HANDLE_ARRAY(128); + HANDLE_ARRAY(256); + HANDLE_ARRAY(512); + HANDLE_ARRAY(1024); + default: + ss << "??"; + } + +#undef HANDLE_INTTYPE +#undef HANDLE_ARRAY + + return ss.str(); +#else + panic("KVM_GET_ONE_REG is unsupported on this platform.\n"); +#endif +} + +Tick +BaseKvmCPU::handleKvmExit() +{ + DPRINTF(KvmRun, "handleKvmExit (exit_reason: %i)\n", _kvmRun->exit_reason); + + switch (_kvmRun->exit_reason) { + case KVM_EXIT_UNKNOWN: + return handleKvmExitUnknown(); + + case KVM_EXIT_EXCEPTION: + return handleKvmExitException(); + + case KVM_EXIT_IO: + ++numIO; + return handleKvmExitIO(); + + case KVM_EXIT_HYPERCALL: + ++numHypercalls; + return handleKvmExitHypercall(); + + case KVM_EXIT_HLT: + /* The guest has halted and is waiting for interrupts */ + DPRINTF(Kvm, "handleKvmExitHalt\n"); + ++numHalt; + + // Suspend the thread until the next interrupt arrives + thread->suspend(); + + // This is actually ignored since the thread is suspended. + return 0; + + case KVM_EXIT_MMIO: + /* Service memory mapped IO requests */ + DPRINTF(KvmIO, "KVM: Handling MMIO (w: %u, addr: 0x%x, len: %u)\n", + _kvmRun->mmio.is_write, + _kvmRun->mmio.phys_addr, _kvmRun->mmio.len); + + ++numMMIO; + return doMMIOAccess(_kvmRun->mmio.phys_addr, _kvmRun->mmio.data, + _kvmRun->mmio.len, _kvmRun->mmio.is_write); + + case KVM_EXIT_IRQ_WINDOW_OPEN: + return handleKvmExitIRQWindowOpen(); + + case KVM_EXIT_FAIL_ENTRY: + return handleKvmExitFailEntry(); + + case KVM_EXIT_INTR: + /* KVM was interrupted by a signal, restart it in the next + * tick. */ + return 0; + + case KVM_EXIT_INTERNAL_ERROR: + panic("KVM: Internal error (suberror: %u)\n", + _kvmRun->internal.suberror); + + default: + panic("KVM: Unexpected exit (exit_reason: %u)\n", _kvmRun->exit_reason); + } +} + +Tick +BaseKvmCPU::handleKvmExitIO() +{ + panic("KVM: Unhandled guest IO (dir: %i, size: %i, port: 0x%x, count: %i)\n", + _kvmRun->io.direction, _kvmRun->io.size, + _kvmRun->io.port, _kvmRun->io.count); +} + +Tick +BaseKvmCPU::handleKvmExitHypercall() +{ + panic("KVM: Unhandled hypercall\n"); +} + +Tick +BaseKvmCPU::handleKvmExitIRQWindowOpen() +{ + warn("KVM: Unhandled IRQ window.\n"); + return 0; +} + + +Tick +BaseKvmCPU::handleKvmExitUnknown() +{ + panic("KVM: Unknown error when starting vCPU (hw reason: 0x%llx)\n", + _kvmRun->hw.hardware_exit_reason); +} + +Tick +BaseKvmCPU::handleKvmExitException() +{ + panic("KVM: Got exception when starting vCPU " + "(exception: %u, error_code: %u)\n", + _kvmRun->ex.exception, _kvmRun->ex.error_code); +} + +Tick +BaseKvmCPU::handleKvmExitFailEntry() +{ + panic("KVM: Failed to enter virtualized mode (hw reason: 0x%llx)\n", + _kvmRun->fail_entry.hardware_entry_failure_reason); +} + +Tick +BaseKvmCPU::doMMIOAccess(Addr paddr, void *data, int size, bool write) +{ + mmio_req.setPhys(paddr, size, + 0, /* flags */ + dataMasterId()); + + const MemCmd cmd(write ? MemCmd::WriteReq : MemCmd::ReadReq); + Packet pkt(&mmio_req, cmd); + pkt.dataStatic(data); + return dataPort.sendAtomic(&pkt); +} + +int +BaseKvmCPU::ioctl(int request, long p1) const +{ + if (vcpuFD == -1) + panic("KVM: CPU ioctl called before initialization\n"); + + return ::ioctl(vcpuFD, request, p1); +} + +Tick +BaseKvmCPU::flushCoalescedMMIO() +{ + if (!mmioRing) + return 0; + + DPRINTF(KvmIO, "KVM: Flushing the coalesced MMIO ring buffer\n"); + + // TODO: We might need to do synchronization when we start to + // support multiple CPUs + Tick ticks(0); + while (mmioRing->first != mmioRing->last) { + struct kvm_coalesced_mmio &ent( + mmioRing->coalesced_mmio[mmioRing->first]); + + DPRINTF(KvmIO, "KVM: Handling coalesced MMIO (addr: 0x%x, len: %u)\n", + ent.phys_addr, ent.len); + + ++numCoalescedMMIO; + ticks += doMMIOAccess(ent.phys_addr, ent.data, ent.len, true); + + mmioRing->first = (mmioRing->first + 1) % KVM_COALESCED_MMIO_MAX; + } + + return ticks; +} + +void +BaseKvmCPU::setupSignalHandler() +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = onTimerOverflow; + sa.sa_flags = SA_SIGINFO | SA_RESTART; + if (sigaction(KVM_TIMER_SIGNAL, &sa, NULL) == -1) + panic("KVM: Failed to setup vCPU signal handler\n"); +} + +void +BaseKvmCPU::setupCounters() +{ + DPRINTF(Kvm, "Attaching cycle counter...\n"); + PerfKvmCounterConfig cfgCycles(PERF_TYPE_HARDWARE, + PERF_COUNT_HW_CPU_CYCLES); + cfgCycles.disabled(true) + .pinned(true); + 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); +} + +void +BaseKvmCPU::startCounters() +{ + // 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(); +} + +void +BaseKvmCPU::stopCounters() +{ + hwCycles.stop(); +} diff --git a/src/cpu/kvm/base.hh b/src/cpu/kvm/base.hh new file mode 100644 index 000000000..1424038a9 --- /dev/null +++ b/src/cpu/kvm/base.hh @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#ifndef __CPU_KVM_BASE_HH__ +#define __CPU_KVM_BASE_HH__ + +#include <memory> + +#include "base/statistics.hh" +#include "cpu/kvm/perfevent.hh" +#include "cpu/kvm/timer.hh" +#include "cpu/kvm/vm.hh" +#include "cpu/base.hh" +#include "cpu/simple_thread.hh" + +/** Signal to use to trigger time-based exits from KVM */ +#define KVM_TIMER_SIGNAL SIGRTMIN + +// forward declarations +class ThreadContext; +struct BaseKvmCPUParams; + +/** + * Base class for KVM based CPU models + * + * All architecture specific KVM implementation should inherit from + * this class. The most basic CPU models only need to override the + * updateKvmState() and updateThreadContext() methods to implement + * state synchronization between gem5 and KVM. + * + * The architecture specific implementation is also responsible for + * delivering interrupts into the VM. This is typically done by + * overriding tick() and checking the thread context before entering + * into the VM. In order to deliver an interrupt, the implementation + * then calls KvmVM::setIRQLine() or BaseKvmCPU::kvmInterrupt() + * depending on the specifics of the underlying hardware/drivers. + */ +class BaseKvmCPU : public BaseCPU +{ + public: + BaseKvmCPU(BaseKvmCPUParams *params); + virtual ~BaseKvmCPU(); + + void init(); + void startup(); + void regStats(); + + void serializeThread(std::ostream &os, ThreadID tid); + void unserializeThread(Checkpoint *cp, const std::string §ion, + ThreadID tid); + + unsigned int drain(DrainManager *dm); + void drainResume(); + + void switchOut(); + void takeOverFrom(BaseCPU *cpu); + + void verifyMemoryMode() const; + + CpuPort &getDataPort() { return dataPort; } + CpuPort &getInstPort() { return instPort; } + + void wakeup(); + void activateContext(ThreadID thread_num, Cycles delay); + void suspendContext(ThreadID thread_num); + void deallocateContext(ThreadID thread_num); + void haltContext(ThreadID thread_num); + + Counter totalInsts() const; + Counter totalOps() const; + + /** Dump the internal state to the terminal. */ + virtual void dump(); + + /** SimpleThread object, provides all the architectural state. */ + SimpleThread *thread; + + /** ThreadContext object, provides an interface for external + * objects to modify this thread's state. + */ + ThreadContext *tc; + + KvmVM &vm; + + protected: + enum Status { + /** Context not scheduled in KVM */ + Idle, + /** Running normally */ + Running, + }; + + /** CPU run state */ + Status _status; + + /** + * Execute the CPU until the next event in the main event queue or + * until the guest needs service from gem5. + * + * @note This method is virtual in order to allow implementations + * to check for architecture specific events (e.g., interrupts) + * before entering the VM. + */ + virtual void tick(); + + /** + * Request KVM to run the guest for a given number of ticks. The + * method returns the approximate number of ticks executed. + * + * @note The returned number of ticks can be both larger or + * smaller than the requested number of ticks. A smaller number + * can, for example, occur when the guest executes MMIO. A larger + * number is typically due to performance counter inaccuracies. + * + * @param ticks Number of ticks to execute + * @return Number of ticks executed (see note) + */ + Tick kvmRun(Tick ticks); + + /** + * Get a pointer to the kvm_run structure containing all the input + * and output parameters from kvmRun(). + */ + struct kvm_run *getKvmRunState() { return _kvmRun; }; + + /** + * Retrieve a pointer to guest data stored at the end of the + * kvm_run structure. This is mainly used for PIO operations + * (KVM_EXIT_IO). + * + * @param offset Offset as specified by the kvm_run structure + * @return Pointer to guest data + */ + uint8_t *getGuestData(uint64_t offset) const { + return (uint8_t *)_kvmRun + offset; + }; + + /** + * @addtogroup KvmInterrupts + * @{ + */ + /** + * Send a non-maskable interrupt to the guest + * + * @note The presence of this call depends on Kvm::capUserNMI(). + */ + void kvmNonMaskableInterrupt(); + + /** + * Send a normal interrupt to the guest + * + * @note Make sure that ready_for_interrupt_injection in kvm_run + * is set prior to calling this function. If not, an interrupt + * window must be requested by setting request_interrupt_window in + * kvm_run to 1 and restarting the guest. + * + * @param interrupt Structure describing the interrupt to send + */ + void kvmInterrupt(const struct kvm_interrupt &interrupt); + + /** @} */ + + /** @{ */ + /** + * Get/Set the register state of the guest vCPU + * + * KVM has two different interfaces for accessing the state of the + * guest CPU. One interface updates 'normal' registers and one + * updates 'special' registers. The distinction between special + * and normal registers isn't very clear and is architecture + * dependent. + */ + void getRegisters(struct kvm_regs ®s) const; + void setRegisters(const struct kvm_regs ®s); + void getSpecialRegisters(struct kvm_sregs ®s) const; + void setSpecialRegisters(const struct kvm_sregs ®s); + /** @} */ + + /** @{ */ + /** + * Get/Set the guest FPU/vector state + */ + void getFPUState(struct kvm_fpu &state) const; + void setFPUState(const struct kvm_fpu &state); + /** @} */ + + /** @{ */ + /** + * Get/Set single register using the KVM_(SET|GET)_ONE_REG API. + * + * @note The presence of this call depends on Kvm::capOneReg(). + */ + void setOneReg(uint64_t id, const void *addr); + void setOneReg(uint64_t id, uint64_t value) { setOneReg(id, &value); } + void setOneReg(uint64_t id, uint32_t value) { setOneReg(id, &value); } + void getOneReg(uint64_t id, void *addr) const; + uint64_t getOneRegU64(uint64_t id) const { + uint64_t value; + getOneReg(id, &value); + return value; + } + uint32_t getOneRegU32(uint64_t id) const { + uint32_t value; + getOneReg(id, &value); + return value; + } + /** @} */ + + /** + * Get and format one register for printout. + * + * This function call getOneReg() to retrieve the contents of one + * register and automatically formats it for printing. + * + * @note The presence of this call depends on Kvm::capOneReg(). + */ + std::string getAndFormatOneReg(uint64_t id) const; + + /** @{ */ + /** + * Update the KVM state from the current thread context + * + * The base CPU calls this method before starting the guest CPU + * when the contextDirty flag is set. The architecture dependent + * CPU implementation is expected to update all guest state + * (registers, special registers, and FPU state). + */ + virtual void updateKvmState() = 0; + + /** + * Update the current thread context with the KVM state + * + * The base CPU after the guest updates any of the KVM state. In + * practice, this happens after kvmRun is called. The architecture + * dependent code is expected to read the state of the guest CPU + * and update gem5's thread state. + */ + virtual void updateThreadContext() = 0; + /** @} */ + + /** @{ */ + /** + * Main kvmRun exit handler, calls the relevant handleKvmExit* + * depending on exit type. + * + * @return Number of ticks spent servicing the exit request + */ + virtual Tick handleKvmExit(); + + /** + * The guest performed a legacy IO request (out/inp on x86) + * + * @return Number of ticks spent servicing the IO request + */ + virtual Tick handleKvmExitIO(); + + /** + * The guest requested a monitor service using a hypercall + * + * @return Number of ticks spent servicing the hypercall + */ + virtual Tick handleKvmExitHypercall(); + + /** + * The guest exited because an interrupt window was requested + * + * The guest exited because an interrupt window was requested + * (request_interrupt_window in the kvm_run structure was set to 1 + * before calling kvmRun) and it is now ready to receive + * + * @return Number of ticks spent servicing the IRQ + */ + virtual Tick handleKvmExitIRQWindowOpen(); + + /** + * An unknown architecture dependent error occurred when starting + * the vCPU + * + * The kvm_run data structure contains the hardware error + * code. The defaults behavior of this method just prints the HW + * error code and panics. Architecture dependent implementations + * may want to override this method to provide better, + * hardware-aware, error messages. + * + * @return Number of ticks delay the next CPU tick + */ + virtual Tick handleKvmExitUnknown(); + + /** + * An unhandled virtualization exception occured + * + * Some KVM virtualization drivers return unhandled exceptions to + * the user-space monitor. This interface is currently only used + * by the Intel VMX KVM driver. + * + * @return Number of ticks delay the next CPU tick + */ + virtual Tick handleKvmExitException(); + + /** + * KVM failed to start the virtualized CPU + * + * The kvm_run data structure contains the hardware-specific error + * code. + * + * @return Number of ticks delay the next CPU tick + */ + virtual Tick handleKvmExitFailEntry(); + /** @} */ + + /** + * Inject a memory mapped IO request into gem5 + * + * @param paddr Physical address + * @param data Pointer to the source/destination buffer + * @param size Memory access size + * @param write True if write, False if read + * @return Number of ticks spent servicing the memory access + */ + Tick doMMIOAccess(Addr paddr, void *data, int size, bool write); + + + /** + * @addtogroup KvmIoctl + * @{ + */ + /** + * vCPU ioctl interface. + * + * @param request KVM vCPU request + * @param p1 Optional request parameter + * + * @return -1 on error (error number in errno), ioctl dependent + * value otherwise. + */ + int ioctl(int request, long p1) const; + int ioctl(int request, void *p1) const { + return ioctl(request, (long)p1); + } + int ioctl(int request) const { + return ioctl(request, 0L); + } + /** @} */ + + /** Port for data requests */ + CpuPort dataPort; + + /** Unused dummy port for the instruction interface */ + CpuPort instPort; + + /** Pre-allocated MMIO memory request */ + Request mmio_req; + + /** + * Is the gem5 context dirty? Set to true to force an update of + * the KVM vCPU state upon the next call to kvmRun(). + */ + bool contextDirty; + + /** KVM internal ID of the vCPU */ + const long vcpuID; + + private: + struct TickEvent : public Event + { + BaseKvmCPU &cpu; + + TickEvent(BaseKvmCPU &c) + : Event(CPU_Tick_Pri), cpu(c) {} + + void process() { cpu.tick(); } + + const char *description() const { + return "BaseKvmCPU tick"; + } + }; + + /** + * Service MMIO requests in the mmioRing. + * + * + * @return Number of ticks spent servicing the MMIO requests in + * the MMIO ring buffer + */ + Tick flushCoalescedMMIO(); + + /** + * Setup a signal handler to catch the timer signal used to + * switch back to the monitor. + */ + void setupSignalHandler(); + + /** Setup hardware performance counters */ + void setupCounters(); + + /** @{ */ + /** Start/stop counting HW performance events */ + void startCounters(); + void stopCounters(); + /** @} */ + + /** KVM vCPU file descriptor */ + int vcpuFD; + /** Size of MMAPed kvm_run area */ + int vcpuMMapSize; + /** + * Pointer to the kvm_run structure used to communicate parameters + * with KVM. + * + * @note This is the base pointer of the MMAPed KVM region. The + * first page contains the kvm_run structure. Subsequent pages may + * contain other data such as the MMIO ring buffer. + */ + struct kvm_run *_kvmRun; + /** + * Coalesced MMIO ring buffer. NULL if coalesced MMIO is not + * supported. + */ + struct kvm_coalesced_mmio_ring *mmioRing; + /** Cached page size of the host */ + const long pageSize; + + TickEvent tickEvent; + + /** @{ */ + /** Guest performance counters */ + PerfKvmCounter hwCycles; + PerfKvmCounter hwInstructions; + /** @} */ + + /** + * Timer used to force execution into the monitor after a + * specified number of simulation tick equivalents have executed + * in the guest. This counter generates the signal specified by + * KVM_TIMER_SIGNAL. + */ + std::unique_ptr<BaseKvmTimer> runTimer; + + float hostFactor; + + public: + /* @{ */ + Stats::Scalar numVMExits; + Stats::Scalar numMMIO; + Stats::Scalar numCoalescedMMIO; + Stats::Scalar numIO; + Stats::Scalar numHalt; + Stats::Scalar numInterrupts; + Stats::Scalar numHypercalls; + /* @} */ +}; + +#endif diff --git a/src/cpu/kvm/perfevent.cc b/src/cpu/kvm/perfevent.cc new file mode 100644 index 000000000..cd8b970e9 --- /dev/null +++ b/src/cpu/kvm/perfevent.cc @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <fcntl.h> +#include <syscall.h> +#include <unistd.h> + +#include <cassert> +#include <cerrno> +#include <csignal> +#include <cstring> + +#include "base/misc.hh" +#include "perfevent.hh" + +PerfKvmCounterConfig::PerfKvmCounterConfig(uint32_t type, uint64_t config) +{ + memset(&attr, 0, sizeof(attr)); + + attr.size = PERF_ATTR_SIZE_VER0; + attr.type = type; + attr.config = config; +} + +PerfKvmCounterConfig::~PerfKvmCounterConfig() +{ +} + + +PerfKvmCounter::PerfKvmCounter(PerfKvmCounterConfig &config, pid_t tid) + : fd(-1), ringBuffer(NULL), pageSize(-1) +{ + attach(config, tid, -1); +} + +PerfKvmCounter::PerfKvmCounter(PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter &parent) + : fd(-1), ringBuffer(NULL), pageSize(-1) +{ + attach(config, tid, parent); +} + +PerfKvmCounter::PerfKvmCounter() + : fd(-1), ringBuffer(NULL), pageSize(-1) +{ +} + +PerfKvmCounter::~PerfKvmCounter() +{ + if (attached()) + detach(); +} + +void +PerfKvmCounter::detach() +{ + assert(attached()); + + if (munmap(ringBuffer, ringNumPages * pageSize) == -1) + warn("PerfKvmCounter: Failed to unmap ring buffer (%i)\n", + errno); + close(fd); + + fd = -1; + ringBuffer = NULL; +} + +void +PerfKvmCounter::start() +{ + if (ioctl(PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) + panic("KVM: Failed to enable performance counters (%i)\n", errno); +} + +void +PerfKvmCounter::stop() +{ + if (ioctl(PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) + panic("KVM: Failed to disable performance counters (%i)\n", errno); +} + +void +PerfKvmCounter::period(uint64_t period) +{ + if (ioctl(PERF_EVENT_IOC_PERIOD, &period) == -1) + panic("KVM: Failed to set period of performance counter (%i)\n", errno); +} + +void +PerfKvmCounter::refresh(int refresh) +{ + if (ioctl(PERF_EVENT_IOC_REFRESH, refresh) == -1) + panic("KVM: Failed to refresh performance counter (%i)\n", errno); +} + +uint64_t +PerfKvmCounter::read() const +{ + uint64_t value; + + read(&value, sizeof(uint64_t)); + return value; +} + +void +PerfKvmCounter::enableSignals(pid_t tid, int signal) +{ + struct f_owner_ex sigowner; + + sigowner.type = F_OWNER_TID; + sigowner.pid = tid; + + if (fcntl(F_SETOWN_EX, &sigowner) == -1 || + fcntl(F_SETSIG, signal) == -1 || + fcntl(F_SETFL, O_ASYNC) == -1) + panic("PerfKvmCounter: Failed to enable signals for counter (%i)\n", + errno); +} + +void +PerfKvmCounter::attach(PerfKvmCounterConfig &config, + pid_t tid, int group_fd) +{ + assert(!attached()); + + fd = syscall(__NR_perf_event_open, + &config.attr, tid, + -1, // CPU (-1 => Any CPU that the task happens to run on) + group_fd, + 0); // Flags + if (fd == -1) + panic("PerfKvmCounter::open failed (%i)\n", errno); + + mmapPerf(1); +} + +pid_t +PerfKvmCounter::gettid() +{ + return syscall(__NR_gettid); +} + +void +PerfKvmCounter::mmapPerf(int pages) +{ + assert(attached()); + assert(ringBuffer == NULL); + + if (pageSize == -1) { + pageSize = sysconf(_SC_PAGE_SIZE); + if (pageSize == -1) + panic("PerfKvmCounter: Failed to determine page size (%i)\n", + errno); + } + + ringNumPages = pages + 1; + ringBuffer = (struct perf_event_mmap_page *)mmap( + NULL, ringNumPages * 4096, + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, 0); + if (ringBuffer == MAP_FAILED) + panic("PerfKvmCounter: MMAP failed (%i)\n", + errno); +} + +int +PerfKvmCounter::fcntl(int cmd, long p1) +{ + assert(attached()); + return ::fcntl(fd, cmd, p1); +} + +int +PerfKvmCounter::ioctl(int request, long p1) +{ + assert(attached()); + return ::ioctl(fd, request, p1); +} + +void +PerfKvmCounter::read(void *buf, size_t size) const +{ + char *_buf = (char *)buf; + size_t _size = size; + + assert(attached()); + + do { + ssize_t ret; + ret = ::read(fd, _buf, _size); + switch (ret) { + case -1: + if (errno != EAGAIN) + panic("PerfKvmCounter::read failed (%i)\n", errno); + break; + + case 0: + panic("PerfKvmCounter::read unexpected EOF.\n"); + + default: + _size -= ret; + _buf += ret; + break; + } + } while(_size); +} diff --git a/src/cpu/kvm/perfevent.hh b/src/cpu/kvm/perfevent.hh new file mode 100644 index 000000000..8242cc071 --- /dev/null +++ b/src/cpu/kvm/perfevent.hh @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#ifndef __CPU_KVM_PERFEVENT_HH__ +#define __CPU_KVM_PERFEVENT_HH__ + +#include <linux/perf_event.h> +#include <sys/types.h> + +#include <inttypes.h> + +/** + * PerfEvent counter configuration. + */ +class PerfKvmCounterConfig +{ + public: + /** + * Initialize PerfEvent counter configuration structure + * + * PerfEvent has the concept of counter types, which is a way to + * abstract hardware performance counters or access software + * events. The type field in the configuration specifies what type + * of counter this is. For hardware performance counters, it's + * typically PERF_TYPE_HARDWARE, PERF_TYPE_HW_CACHE, or + * PERF_TYPE_RAW. + * + * The 'config' field has different meanings depending on the type + * of counter. Possible values are listed in perf_event.h in the + * kernel headers. When using raw counters, the value is the raw + * value written to the performance counter configuration register + * (some bits dealing with sampling and similar features are + * usually masked). + * + * @param type Counter type. + * @param config Counter configuration + */ + PerfKvmCounterConfig(uint32_t type, uint64_t config); + ~PerfKvmCounterConfig(); + + /** + * Set the initial sample period (overflow count) of an event. If + * this is set to 0, the event acts as a normal counting event and + * does not trigger overflows. + * + * @param period Number of counter events before the counter + * overflows + */ + PerfKvmCounterConfig &samplePeriod(uint64_t period) { + attr.freq = 0; + attr.sample_period = period; + return *this; + } + + /** + * Set the number of samples that need to be triggered before + * reporting data as being available on the perf event + * FD. Defaults to 0, which disables overflow reporting. + * + * @param events Number of overflows before signaling a wake up + */ + PerfKvmCounterConfig &wakeupEvents(uint32_t events) { + attr.watermark = 0; + attr.wakeup_events = events; + return *this; + } + + /** + * Don't start the performance counter automatically when + * attaching it. + * + * @param val true to disable, false to enable the counter + */ + PerfKvmCounterConfig &disabled(bool val) { + attr.disabled = val; + return *this; + } + + /** + * Force the group to be on the active all the time (i.e., + * disallow multiplexing). + * + * Only applies to group leaders. + * + * @param val true to pin the counter + */ + PerfKvmCounterConfig &pinned(bool val) { + attr.pinned = val; + return *this; + } + + /** Underlying perf_event_attr structure describing the counter */ + struct perf_event_attr attr; +}; + +/** + * An instance of a performance counter. + */ +class PerfKvmCounter +{ +public: + /** + * Create and attach a new counter group. + * + * @param config Counter configuration + * @param tid Thread to sample (0 indicates current thread) + */ + PerfKvmCounter(PerfKvmCounterConfig &config, pid_t tid); + /** + * Create and attach a new counter and make it a member of an + * exist counter group. + * + * @param config Counter configuration + * @param tid Thread to sample (0 indicates current thread) + * @param parent Group leader + */ + PerfKvmCounter(PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter &parent); + /** + * Create a new counter, but don't attach it. + */ + PerfKvmCounter(); + ~PerfKvmCounter(); + + + /** + * Attach a counter. + * + * @note This operation is only supported if the counter isn't + * already attached. + * + * @param config Counter configuration + * @param tid Thread to sample (0 indicates current thread) + */ + void attach(PerfKvmCounterConfig &config, pid_t tid) { + attach(config, tid, -1); + } + + /** + * Attach a counter and make it a member of an existing counter + * group. + * + * @note This operation is only supported if the counter isn't + * already attached. + * + * @param config Counter configuration + * @param tid Thread to sample (0 indicates current thread) + * @param parent Group leader + */ + void attach(PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter &parent) { + attach(config, tid, parent.fd); + } + + /** Detach a counter from PerfEvent. */ + void detach(); + + /** Check if a counter is attached. */ + bool attached() const { return fd != -1; } + + /** + * Start counting. + * + * @note If this counter is a group leader, it will start the + * entire group. + */ + void start(); + + /** + * Stop counting. + * + * @note If this counter is a group leader, it will stop the + * entire group. + */ + void stop(); + + /** + * Update the period of an overflow counter. + * + * @warning This ioctl has some pretty bizarre semantics. It seems + * like the new period isn't effective until after the next + * counter overflow. If you use this method to change the sample + * period, you will see one sample with the old period and then + * start sampling with the new period. + * + * @warning This method doesn't work at all on some 2.6.3x kernels + * since it has inverted check for the return value when copying + * parameters from userspace. + * + * @param period Overflow period in events + */ + void period(uint64_t period); + + /** + * Enable a counter for a fixed number of events. + * + * When this method is called, perf event enables the counter if + * it was disabled. It then leaves the counter enabled until it + * has overflowed a refresh times. + * + * @note This does not update the period of the counter. + * + * @param refresh Number of overflows before disabling the + * counter. + */ + void refresh(int refresh); + + /** + * Read the current value of a counter. + */ + uint64_t read() const; + + /** + * Enable signal delivery to a thread on counter overflow. + * + * @param tid Thread to deliver signal to + * @param signal Signal to send upon overflow + */ + void enableSignals(pid_t tid, int signal); + + /** + * Enable signal delivery on counter overflow. Identical to + * enableSignals(pid_t) when called with the current TID as its + * parameter. + * + * @param signal Signal to send upon overflow + */ + void enableSignals(int signal) { enableSignals(gettid(), signal); } + +private: + // Disallow copying + PerfKvmCounter(const PerfKvmCounter &that); + // Disallow assignment + PerfKvmCounter &operator=(const PerfKvmCounter &that); + + void attach(PerfKvmCounterConfig &config, pid_t tid, int group_fd); + + /** + * Get the TID of the current thread. + * + * @return Current thread's TID + */ + pid_t gettid(); + + /** + * MMAP the PerfEvent file descriptor. + * + * @note We currently don't use the ring buffer, but PerfEvent + * requires this to be mapped for overflow handling to work. + * + * @note Overflow handling requires at least one buf_page to be + * mapped. + * + * @param pages number of pages in circular sample buffer. Must be + * an even power of 2. + */ + void mmapPerf(int pages); + + /** @{ */ + /** + * PerfEvent fnctl interface. + * + * @param cmd fcntl command + * @param p1 Request parameter + * + * @return -1 on error (error number in errno), ioctl dependent + * value otherwise. + */ + int fcntl(int cmd, long p1); + int fcntl(int cmd, void *p1) { return fcntl(cmd, (long)p1); } + /** @} */ + + /** @{ */ + /** + * PerfEvent ioctl interface. + * + * @param request PerfEvent request + * @param p1 Optional request parameter + * + * @return -1 on error (error number in errno), ioctl dependent + * value otherwise. + */ + int ioctl(int request, long p1); + int ioctl(int request, void *p1) { return ioctl(request, (long)p1); } + int ioctl(int request) { return ioctl(request, 0L); } + /** @} */ + + /** + * Perform a read from the counter file descriptor. + * + * @param buf Destination buffer + * @param size Amount of data to read + */ + void read(void *buf, size_t size) const; + + /** + * PerfEvent file descriptor associated with counter. -1 if not + * attached to PerfEvent. + */ + int fd; + + /** Memory mapped PerfEvent sample ring buffer */ + struct perf_event_mmap_page *ringBuffer; + /** Total number of pages in ring buffer */ + int ringNumPages; + + /** Cached host page size */ + long pageSize; +}; + +#endif diff --git a/src/cpu/kvm/timer.cc b/src/cpu/kvm/timer.cc new file mode 100644 index 000000000..059d70f6b --- /dev/null +++ b/src/cpu/kvm/timer.cc @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#include <csignal> +#include <ctime> + +#include "base/misc.hh" +#include "base/trace.hh" +#include "cpu/kvm/timer.hh" +#include "debug/KvmTimer.hh" + + +PosixKvmTimer::PosixKvmTimer(int signo, clockid_t clockID, + float hostFactor, Tick hostFreq) + : BaseKvmTimer(signo, hostFactor, hostFreq), + clockID(clockID) +{ + struct sigevent sev; + + // TODO: We should request signal delivery to thread instead of + // the process here. Unfortunately this seems to be broken, or at + // least not work as specified in the man page. + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signo; + sev.sigev_value.sival_ptr = NULL; + if (timer_create(clockID, &sev, &timer) == -1) + panic("timer_create"); +} + +PosixKvmTimer::~PosixKvmTimer() +{ + timer_delete(timer); +} + +void +PosixKvmTimer::arm(Tick ticks) +{ + struct itimerspec ts; + memset(&ts, 0, sizeof(ts)); + + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + ts.it_value.tv_sec = hostNs(ticks) / 1000000000ULL; + ts.it_value.tv_nsec = hostNs(ticks) % 1000000000ULL; + + DPRINTF(KvmTimer, "Arming POSIX timer: %i ticks (%is%ins)\n", + ticks, ts.it_value.tv_sec, ts.it_value.tv_nsec); + + if (timer_settime(timer, 0, &ts, NULL) == -1) + panic("PosixKvmTimer: Failed to arm timer\n"); +} + +void +PosixKvmTimer::disarm() +{ + struct itimerspec ts; + memset(&ts, 0, sizeof(ts)); + + DPRINTF(KvmTimer, "Disarming POSIX timer\n"); + + if (timer_settime(timer, 0, &ts, NULL) == -1) + panic("PosixKvmTimer: Failed to disarm timer\n"); +} + +Tick +PosixKvmTimer::calcResolution() +{ + struct timespec ts; + + if (clock_getres(clockID, &ts) == -1) + panic("PosixKvmTimer: Failed to get timer resolution\n"); + + Tick resolution(ticksFromHostNs(ts.tv_sec * 1000000000ULL + ts.tv_nsec)); + + return resolution; +} diff --git a/src/cpu/kvm/timer.hh b/src/cpu/kvm/timer.hh new file mode 100644 index 000000000..a5105e7fa --- /dev/null +++ b/src/cpu/kvm/timer.hh @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#ifndef __CPU_KVM_TIMER_HH__ +#define __CPU_KVM_TIMER_HH__ + +#include <ctime> + +#include "sim/core.hh" + +/** + * Timer functions to interrupt VM execution after a number of + * simulation ticks. The timer allows scaling of the host time to take + * performance differences between the simulated and real CPU into + * account. + * + * The performance scaling factor is ratio between the target's CPI + * and the host's CPI. It is larger than 1 if the host is faster than + * the target and lower than 1 if it is slower. + * + * When the timer times out, it sends a signal to the thread that + * started the timer. The signal forces KVM to drop out of the system + * call that started the guest and hands control to gem5. + */ +class BaseKvmTimer +{ + public: + /** + * Setup basic timer functionality shared by all timer + * implementations. + * + * @param signo Signal to deliver + * @param hostFactor Performance scaling factor + * @param hostFreq Clock frequency of the host + */ + BaseKvmTimer(int signo, float hostFactor, Tick hostFreq) + : signo(signo), _resolution(0), + hostFactor(hostFactor), hostFreq(hostFreq) {}; + virtual ~BaseKvmTimer() {}; + + /** + * Arm the timer so that it fires after a certain number of ticks. + * + * @note A timer implementation is free to convert between + * simulation ticks and virtualized time using any method it + * chooses. The accuracy of the timer therefore depends on what it + * measures, an accurate timer implementation should measure the + * number of cycles or instructions executed in the guest. If such + * counters are unavailable, it may fallback to wall clock time. + * + * @param ticks Number of ticks until the timer fires + */ + virtual void arm(Tick ticks) = 0; + /** + * Disarm the timer. + * + * When this method has returned, the timer may no longer deliver + * signals upon timeout. + */ + virtual void disarm() = 0; + + /** + * Determine the resolution of the timer in ticks. This method is + * mainly used to determine the smallest number of ticks the timer + * can wait before triggering a signal. + * + * @return Minimum number of ticks the timer can resolve + */ + Tick resolution() { + if (_resolution == 0) + _resolution = calcResolution(); + return _resolution; + } + + /** + * Convert cycles executed on the host into Ticks executed in the + * simulator. Scales the results using the hostFactor to take CPU + * performance differences into account. + * + * @return Host cycles executed in VM converted to simulation ticks + */ + Tick ticksFromHostCycles(uint64_t cycles) { + return cycles * hostFactor * hostFreq; + } + + /** + * Convert nanoseconds executed on the host into Ticks executed in + * the simulator. Scales the results using the hostFactor to take + * CPU performance differences into account. + * + * @return Nanoseconds executed in VM converted to simulation ticks + */ + Tick ticksFromHostNs(uint64_t ns) { + return ns * hostFactor * SimClock::Float::ns; + } + + protected: + /** + * Calculate the timer resolution, used by resolution() which + * caches the result. + * + * @return Minimum number of ticks the timer can resolve + */ + virtual Tick calcResolution() = 0; + + /** + * Convert a time in simulator ticks to host nanoseconds. + * + * @return Simulation ticks converted into nanoseconds on the host + */ + uint64_t hostNs(Tick ticks) { + return ticks / (SimClock::Float::ns * hostFactor); + } + + /** + * Convert a time in simulator ticks to host cycles + * + * + * @return Simulation ticks converted into CPU cycles on the host + */ + uint64_t hostCycles(Tick ticks) { + return ticks / (hostFreq * hostFactor); + } + + /** Signal to deliver when the timer times out */ + int signo; + + private: + /** Cached resolution */ + mutable Tick _resolution; + + /** Performance scaling factor */ + float hostFactor; + /** Host frequency */ + Tick hostFreq; +}; + +/** + * Timer based on standard POSIX timers. The POSIX timer API supports + * several different clock with different characteristics. + * + * @note It might be tempting to use + * CLOCK_(THREAD|PROCESS)_CPUTIME_ID, however, this clock usually has + * much lower resolution than the real-time clocks. + */ +class PosixKvmTimer : public BaseKvmTimer +{ + public: + /** + * @param signo Signal to deliver + * @param clockID ID of the clock to use + * @param hostFactor Performance scaling factor + * @param hostFreq Clock frequency of the host + */ + PosixKvmTimer(int signo, clockid_t clockID, + float hostFactor, Tick hostFreq); + ~PosixKvmTimer(); + + void arm(Tick ticks); + void disarm(); + + protected: + Tick calcResolution(); + + private: + clockid_t clockID; + timer_t timer; +}; + +#endif diff --git a/src/cpu/kvm/vm.cc b/src/cpu/kvm/vm.cc new file mode 100644 index 000000000..8e7f6462d --- /dev/null +++ b/src/cpu/kvm/vm.cc @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#include <linux/kvm.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> + +#include <cerrno> + +#include "cpu/kvm/vm.hh" +#include "debug/Kvm.hh" +#include "params/KvmVM.hh" +#include "sim/system.hh" + +#define EXPECTED_KVM_API_VERSION 12 + +#if EXPECTED_KVM_API_VERSION != KVM_API_VERSION +#error Unsupported KVM version +#endif + +Kvm *Kvm::instance = NULL; + +Kvm::Kvm() + : kvmFD(-1), apiVersion(-1), vcpuMMapSize(0) +{ + kvmFD = ::open("/dev/kvm", O_RDWR); + if (kvmFD == -1) + fatal("KVM: Failed to open /dev/kvm\n"); + + apiVersion = ioctl(KVM_GET_API_VERSION); + if (apiVersion != EXPECTED_KVM_API_VERSION) + fatal("KVM: Incompatible API version\n"); + + vcpuMMapSize = ioctl(KVM_GET_VCPU_MMAP_SIZE); + if (vcpuMMapSize == -1) + panic("KVM: Failed to get virtual CPU MMAP size\n"); +} + +Kvm::~Kvm() +{ + close(kvmFD); +} + +Kvm * +Kvm::create() +{ + if (!instance) + instance = new Kvm(); + + return instance; +} + +bool +Kvm::capUserMemory() const +{ + return checkExtension(KVM_CAP_USER_MEMORY) != 0; +} + +bool +Kvm::capSetTSSAddress() const +{ + return checkExtension(KVM_CAP_SET_TSS_ADDR) != 0; +} + +bool +Kvm::capExtendedCPUID() const +{ + return checkExtension(KVM_CAP_EXT_CPUID) != 0; +} + +bool +Kvm::capUserNMI() const +{ +#ifdef KVM_CAP_USER_NMI + return checkExtension(KVM_CAP_USER_NMI) != 0; +#else + return false; +#endif +} + +int +Kvm::capCoalescedMMIO() const +{ + return checkExtension(KVM_CAP_COALESCED_MMIO); +} + +bool +Kvm::capOneReg() const +{ +#ifdef KVM_CAP_ONE_REG + return checkExtension(KVM_CAP_ONE_REG) != 0; +#else + return false; +#endif +} + +bool +Kvm::capIRQChip() const +{ + return checkExtension(KVM_CAP_IRQCHIP) != 0; +} + +bool +Kvm::getSupportedCPUID(struct kvm_cpuid2 &cpuid) const +{ +#if defined(__i386__) || defined(__x86_64__) + if (ioctl(KVM_GET_SUPPORTED_CPUID, (void *)&cpuid) == -1) { + if (errno == E2BIG) + return false; + else + panic("KVM: Failed to get supported CPUID (errno: %i)\n", errno); + } else + return true; +#else + panic("KVM: getSupportedCPUID is unsupported on this platform.\n"); +#endif +} + +int +Kvm::checkExtension(int extension) const +{ + int ret = ioctl(KVM_CHECK_EXTENSION, extension); + if (ret == -1) + panic("KVM: ioctl failed when checking for extension\n"); + return ret; +} + +int +Kvm::ioctl(int request, long p1) const +{ + assert(kvmFD != -1); + + return ::ioctl(kvmFD, request, p1); +} + +int +Kvm::createVM() +{ + int vmFD; + + vmFD = ioctl(KVM_CREATE_VM); + if (vmFD == -1) + panic("Failed to create KVM VM\n"); + + return vmFD; +} + + +KvmVM::KvmVM(KvmVMParams *params) + : SimObject(params), + kvm(), system(params->system), + vmFD(kvm.createVM()), + started(false), + nextVCPUID(0) +{ + /* Setup the coalesced MMIO regions */ + for (int i = 0; i < params->coalescedMMIO.size(); ++i) + coalesceMMIO(params->coalescedMMIO[i]); +} + +KvmVM::~KvmVM() +{ + close(vmFD); +} + +void +KvmVM::cpuStartup() +{ + if (started) + return; + started = true; + + delayedStartup(); +} + +void +KvmVM::delayedStartup() +{ + const std::vector<std::pair<AddrRange, uint8_t*> >&memories( + system->getPhysMem().getBackingStore()); + + DPRINTF(Kvm, "Mapping %i memory region(s)\n", memories.size()); + for (int slot(0); slot < memories.size(); ++slot) { + const AddrRange &range(memories[slot].first); + void *pmem(memories[slot].second); + + if (pmem) { + DPRINTF(Kvm, "Mapping region: 0x%p -> 0x%llx [size: 0x%llx]\n", + pmem, range.start(), range.size()); + + setUserMemoryRegion(slot, pmem, range, 0 /* flags */); + } else { + DPRINTF(Kvm, "Zero-region not mapped: [0x%llx]\n", range.start()); + hack("KVM: Zero memory handled as IO\n"); + } + } +} + +void +KvmVM::setUserMemoryRegion(uint32_t slot, + void *host_addr, AddrRange guest_range, + uint32_t flags) +{ + if (guest_range.interleaved()) + panic("Tried to map an interleaved memory range into a KVM VM.\n"); + + setUserMemoryRegion(slot, host_addr, + guest_range.start(), guest_range.size(), + flags); +} + +void +KvmVM::setUserMemoryRegion(uint32_t slot, + void *host_addr, Addr guest_addr, + uint64_t len, uint32_t flags) +{ + struct kvm_userspace_memory_region m; + + memset(&m, 0, sizeof(m)); + m.slot = slot; + m.flags = flags; + m.guest_phys_addr = (uint64_t)guest_addr; + m.memory_size = len; + m.userspace_addr = (__u64)host_addr; + + if (ioctl(KVM_SET_USER_MEMORY_REGION, (void *)&m) == -1) { + panic("Failed to setup KVM memory region:\n" + "\tHost Address: 0x%p\n" + "\tGuest Address: 0x%llx\n", + "\tSize: %ll\n", + "\tFlags: 0x%x\n", + m.userspace_addr, m.guest_phys_addr, + m.memory_size, m.flags); + } +} + +void +KvmVM::coalesceMMIO(const AddrRange &range) +{ + coalesceMMIO(range.start(), range.size()); +} + +void +KvmVM::coalesceMMIO(Addr start, int size) +{ + struct kvm_coalesced_mmio_zone zone; + + zone.addr = start; + zone.size = size; + zone.pad = 0; + + DPRINTF(Kvm, "KVM: Registering coalesced MMIO region [0x%x, 0x%x]\n", + zone.addr, zone.addr + zone.size - 1); + if (ioctl(KVM_REGISTER_COALESCED_MMIO, (void *)&zone) == -1) + panic("KVM: Failed to register coalesced MMIO region (%i)\n", + errno); +} + +void +KvmVM::setTSSAddress(Addr tss_address) +{ + if (ioctl(KVM_SET_TSS_ADDR, (unsigned long)tss_address) == -1) + panic("KVM: Failed to set VM TSS address\n"); +} + +void +KvmVM::createIRQChip() +{ + if (_hasKernelIRQChip) + panic("KvmVM::createIRQChip called twice.\n"); + + if (ioctl(KVM_CREATE_IRQCHIP) != -1) { + _hasKernelIRQChip = true; + } else { + warn("KVM: Failed to create in-kernel IRQ chip (errno: %i)\n", + errno); + _hasKernelIRQChip = false; + } +} + +void +KvmVM::setIRQLine(uint32_t irq, bool high) +{ + struct kvm_irq_level kvm_level; + + kvm_level.irq = irq; + kvm_level.level = high ? 1 : 0; + + if (ioctl(KVM_IRQ_LINE, &kvm_level) == -1) + panic("KVM: Failed to set IRQ line level (errno: %i)\n", + errno); +} + +int +KvmVM::createVCPU(long vcpuID) +{ + int fd; + + fd = ioctl(KVM_CREATE_VCPU, vcpuID); + if (fd == -1) + panic("KVM: Failed to create virtual CPU"); + + return fd; +} + +long +KvmVM::allocVCPUID() +{ + return nextVCPUID++; +} + +int +KvmVM::ioctl(int request, long p1) const +{ + assert(vmFD != -1); + + return ::ioctl(vmFD, request, p1); +} + + +KvmVM * +KvmVMParams::create() +{ + static bool created = false; + if (created) + warn_once("Use of multiple KvmVMs is currently untested!\n"); + + created = true; + + return new KvmVM(this); +} diff --git a/src/cpu/kvm/vm.hh b/src/cpu/kvm/vm.hh new file mode 100644 index 000000000..67e8e4cbd --- /dev/null +++ b/src/cpu/kvm/vm.hh @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Andreas Sandberg + */ + +#ifndef __CPU_KVM_KVMVM_HH__ +#define __CPU_KVM_KVMVM_HH__ + +#include "base/addr_range.hh" +#include "sim/sim_object.hh" + +// forward declarations +struct KvmVMParams; +class System; + +/** + * @defgroup KvmInterrupts KVM Interrupt handling. + * + * These methods control interrupt delivery to the guest system. + */ + +/** + * @defgroup KvmIoctl KVM low-level ioctl interface. + * + * These methods provide a low-level interface to the underlying KVM + * layer. + */ + +/** + * KVM parent interface + * + * The main Kvm object is used to provide functionality that is not + * specific to a VM or CPU. For example, it allows checking of the + * optional features and creation of VM containers. + */ +class Kvm +{ + friend class KvmVM; + + public: + virtual ~Kvm(); + + Kvm *create(); + + /** Get the version of the KVM API implemented by the kernel. */ + int getAPIVersion() const { return apiVersion; } + /** + * Get the size of the MMAPed parameter area used to communicate + * vCPU parameters between the kernel and userspace. This area, + * amongst other things, contains the kvm_run data structure. + */ + int getVCPUMMapSize() const { return vcpuMMapSize; } + + /** @{ */ + /** Support for KvmVM::setUserMemoryRegion() */ + bool capUserMemory() const; + /** Support for KvmVM::setTSSAddress() */ + bool capSetTSSAddress() const; + /** Support for BaseKvmCPU::setCPUID2 and getSupportedCPUID(). */ + bool capExtendedCPUID() const; + /** Support for BaseKvmCPU::kvmNonMaskableInterrupt(). */ + bool capUserNMI() const; + + /** + * Check if coalesced MMIO is supported and which page in the + * MMAP'ed structure it stores requests in. + * + * @return Offset (in pages) into the mmap'ed vCPU area where the + * MMIO buffer is stored. 0 if unsupported. + */ + int capCoalescedMMIO() const; + + /** + * Support for reading and writing single registers. + * + * @see BaseKvmCPU::getOneReg(), and BaseKvmCPU::setOneReg() + */ + bool capOneReg() const; + + /** + * Support for creating an in-kernel IRQ chip model. + * + * @see KvmVM::createIRQChip() + */ + bool capIRQChip() const; + /** @} */ + + /** + * Get the CPUID features supported by the hardware and Kvm. + * + * @note Requires capExtendedCPUID(). + * + * @return False if the allocation is too small, true on success. + */ + bool getSupportedCPUID(struct kvm_cpuid2 &cpuid) const; + + protected: + /** + * Check for the presence of an extension to the KVM API. + * + * The return value depends on the extension, but is always zero + * if it is unsupported or positive otherwise. Some extensions use + * the return value provide additional data about the extension. + * + * @return 0 if the extension is unsupported, positive integer + * otherwise. + */ + int checkExtension(int extension) const; + + /** + * @addtogroup KvmIoctl + * @{ + */ + /** + * Main VM ioctl interface. + * + * @param request KVM request + * @param p1 Optional request parameter + * + * @return -1 on error (error number in errno), ioctl dependent + * value otherwise. + */ + int ioctl(int request, long p1) const; + int ioctl(int request, void *p1) const { + return ioctl(request, (long)p1); + } + int ioctl(int request) const { + return ioctl(request, 0L); + } + /** @} */ + + private: + // This object is a singleton, so prevent instantiation. + Kvm(); + + // Prevent copying + Kvm(const Kvm &kvm); + // Prevent assignment + Kvm &operator=(const Kvm &kvm); + + /** + * Create a KVM Virtual Machine + * + * @return File descriptor pointing to the VM + */ + int createVM(); + + /** KVM VM file descriptor */ + int kvmFD; + /** KVM API version */ + int apiVersion; + /** Size of the MMAPed vCPU parameter area. */ + int vcpuMMapSize; + + /** Singleton instance */ + static Kvm *instance; +}; + +/** + * KVM VM container + * + * A KVM VM container normally contains all the CPUs in a shared + * memory machine. The VM container handles things like physical + * memory and to some extent interrupts. Normally, the VM API is only + * used for interrupts when the PIC is emulated by the kernel, which + * is a feature we do not use. However, some architectures (notably + * ARM) use the VM interface to deliver interrupts to specific CPUs as + * well. + * + * VM initialization is a bit different from that of other + * SimObjects. When we initialize the VM, we discover all physical + * memory mappings in the system. Since AbstractMem::unserialize + * re-maps the guests memory, we need to make sure that this is done + * after the memory has been re-mapped, but before the vCPUs are + * initialized (KVM requires memory mappings to be setup before CPUs + * can be created). Normally, we would just initialize the VM in + * init() or startup(), however, we can not use init() since this is + * called before AbstractMem::unserialize() and we can not use + * startup() since it must be called before BaseKvmCPU::startup() and + * the simulator framework does not guarantee call order. We therefore + * call cpuStartup() from BaseKvmCPU::startup() instead and execute + * the initialization code once when the first CPU in the VM is + * starting. + */ +class KvmVM : public SimObject +{ + friend class BaseKvmCPU; + + public: + KvmVM(KvmVMParams *params); + virtual ~KvmVM(); + + /** + * Setup a shared three-page memory region used by the internals + * of KVM. This is currently only needed by x86 implementations. + * + * @param tss_address Physical address of the start of the TSS + */ + void setTSSAddress(Addr tss_address); + + /** @{ */ + /** + * Request coalescing MMIO for a memory range. + * + * @param start Physical start address in guest + * @param size Size of the MMIO region + */ + void coalesceMMIO(Addr start, int size); + + /** + * Request coalescing MMIO for a memory range. + * + * @param range Coalesced MMIO range + */ + void coalesceMMIO(const AddrRange &range); + /** @} */ + + /** + * @addtogroup KvmInterrupts + * @{ + */ + /** + * Create an in-kernel interrupt controller + * + * @note This functionality depends on Kvm::capIRQChip(). + */ + void createIRQChip(); + + /** + * Set the status of an IRQ line using KVM_IRQ_LINE. + * + * @note This ioctl is usually only used if the interrupt + * controller is emulated by the kernel (i.e., after calling + * createIRQChip()). Some architectures (e.g., ARM) use it instead + * of BaseKvmCPU::kvmInterrupt(). + * + * @param irq Interrupt number + * @param high Line level (true for high, false for low) + */ + void setIRQLine(uint32_t irq, bool high); + + /** + * Is in-kernel IRQ chip emulation enabled? + */ + bool hasKernelIRQChip() const { return _hasKernelIRQChip; } + /** @} */ + + /** Global KVM interface */ + Kvm kvm; + + protected: + /** + * VM CPU initialization code. + * + * This method is called from BaseKvmCPU::startup() when a CPU in + * the VM executes its BaseKvmCPU::startup() method. The first + * time method is executed on a VM, it calls the delayedStartup() + * method. + */ + void cpuStartup(); + + /** + * Delayed initialization, executed once before the first CPU + * starts. + * + * This method provides a way to do VM initialization once before + * the first CPU in a VM starts. It is needed since some resources + * (e.g., memory mappings) can change in the normal + * SimObject::startup() path. Since the call order of + * SimObject::startup() is not guaranteed, we simply defer some + * initialization until a CPU is about to start. + */ + void delayedStartup(); + + + /** @{ */ + /** + * Setup a region of physical memory in the guest + * + * @param slot KVM memory slot ID (must be unique) + * @param host_addr Memory allocation backing the memory + * @param guest_addr Address in the guest + * @param guest_range Address range used by guest. + * @param len Size of the allocation in bytes + * @param flags Flags (see the KVM API documentation) + */ + void setUserMemoryRegion(uint32_t slot, + void *host_addr, Addr guest_addr, + uint64_t len, uint32_t flags); + void setUserMemoryRegion(uint32_t slot, + void *host_addr, AddrRange guest_range, + uint32_t flags); + /** @} */ + + /** + * Create a new vCPU within a VM. + * + * @param vcpuID ID of the new CPU within the VM. + * @return File descriptor referencing the CPU. + */ + int createVCPU(long vcpuID); + + /** + * Allocate a new vCPU ID within the VM. + * + * The returned vCPU ID is guaranteed to be unique within the + * VM. New IDs are allocated sequentially starting from 0. + * + * @return ID of the new vCPU + */ + long allocVCPUID(); + + /** + * @addtogroup KvmIoctl + * @{ + */ + /** + * KVM VM ioctl interface. + * + * @param request KVM VM request + * @param p1 Optional request parameter + * + * @return -1 on error (error number in errno), ioctl dependent + * value otherwise. + */ + int ioctl(int request, long p1) const; + int ioctl(int request, void *p1) const { + return ioctl(request, (long)p1); + } + int ioctl(int request) const { + return ioctl(request, 0L); + } + /**@}*/ + + private: + // Prevent copying + KvmVM(const KvmVM &vm); + // Prevent assignment + KvmVM &operator=(const KvmVM &vm); + + System *system; + + /** KVM VM file descriptor */ + const int vmFD; + + /** Has delayedStartup() already been called? */ + bool started; + + /** Do we have in-kernel IRQ-chip emulation enabled? */ + bool _hasKernelIRQChip; + + /** Next unallocated vCPU ID */ + long nextVCPUID; +}; + +#endif |