kvm: Basic support for hardware virtualized CPUs

This changeset introduces the architecture independent parts required
to support KVM-accelerated CPUs. It introduces two new simulation
objects:

KvmVM -- The KVM VM is a component shared between all CPUs in a shared
         memory domain. It is typically instantiated as a child of the
         system object in the simulation hierarchy. It provides access
         to KVM VM specific interfaces.

BaseKvmCPU -- Abstract base class for all KVM-based CPUs. Architecture
	      dependent CPU implementations inherit from this class
	      and implement the following methods:

                * updateKvmState() -- Update the
                  architecture-dependent KVM state from the gem5
                  thread context associated with the CPU.

                * updateThreadContext() -- Update the thread context
                  from the architecture-dependent KVM state.

                * dump() -- Dump the KVM state using (optional).

	      In order to deliver interrupts to the guest, CPU
	      implementations typically override the tick() method and
	      check for, and deliver, interrupts prior to entering
	      KVM.

Hardware-virutalized CPU currently have the following limitations:
 * SE mode is not supported.
 * PC events are not supported.
 * Timing statistics are currently very limited. The current approach
   simply scales the host cycles with a user-configurable factor.
 * The simulated system must not contain any caches.
 * Since cycle counts are approximate, there is no way to request an
   exact number of cycles (or instructions) to be executed by the CPU.
 * Hardware virtualized CPUs and gem5 CPUs must not execute at the
   same time in the same simulator instance.
 * Only single-CPU systems can be simulated.
 * Remote GDB connections to the guest system are not supported.

Additionally, m5ops requires an architecture specific interface and
might not be supported.
This commit is contained in:
Andreas Sandberg 2013-04-22 13:20:32 -04:00
parent 005616518c
commit f485ad1908
12 changed files with 3176 additions and 0 deletions

View file

@ -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)

72
src/cpu/kvm/BaseKvmCPU.py Normal file
View file

@ -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")

49
src/cpu/kvm/KvmVM.py Normal file
View file

@ -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")

60
src/cpu/kvm/SConscript Normal file
View file

@ -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')

805
src/cpu/kvm/base.cc Normal file
View file

@ -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 &section,
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 &regs) const
{
if (ioctl(KVM_GET_REGS, &regs) == -1)
panic("KVM: Failed to get guest registers\n");
}
void
BaseKvmCPU::setRegisters(const struct kvm_regs &regs)
{
if (ioctl(KVM_SET_REGS, (void *)&regs) == -1)
panic("KVM: Failed to set guest registers\n");
}
void
BaseKvmCPU::getSpecialRegisters(struct kvm_sregs &regs) const
{
if (ioctl(KVM_GET_SREGS, &regs) == -1)
panic("KVM: Failed to get guest special registers\n");
}
void
BaseKvmCPU::setSpecialRegisters(const struct kvm_sregs &regs)
{
if (ioctl(KVM_SET_SREGS, (void *)&regs) == -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, &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, &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();
}

489
src/cpu/kvm/base.hh Normal file
View file

@ -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 &section,
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 &regs) const;
void setRegisters(const struct kvm_regs &regs);
void getSpecialRegisters(struct kvm_sregs &regs) const;
void setSpecialRegisters(const struct kvm_sregs &regs);
/** @} */
/** @{ */
/**
* 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

246
src/cpu/kvm/perfevent.cc Normal file
View file

@ -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);
}

347
src/cpu/kvm/perfevent.hh Normal file
View file

@ -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

112
src/cpu/kvm/timer.cc Normal file
View file

@ -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;
}

206
src/cpu/kvm/timer.hh Normal file
View file

@ -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

370
src/cpu/kvm/vm.cc Normal file
View file

@ -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);
}

390
src/cpu/kvm/vm.hh Normal file
View file

@ -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