We currently execute instructions in the guest and then handle any IO request right after we break out of the virtualized environment. This has the effect of executing IO requests in the exact same tick as the first instruction in the sequence that was just run. There seem to be cases where this simplification upsets some timing-sensitive devices. This changeset splits execute and IO (and other services) across multiple ticks. This is implemented by adding a separate RunningService state to the CPU state machine. When a VM requires service, it enters into this state and pending IO is then serviced in the future instead of immediately. The delay between getting the request and servicing it depends on the number of cycles executed in the guest, which allows other components to catch up with the CPU.
869 lines
28 KiB
C++
869 lines
28 KiB
C++
/*
|
|
* 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 <algorithm>
|
|
#include <cerrno>
|
|
#include <memory>
|
|
|
|
#include "arch/registers.hh"
|
|
#include "cpu/kvm/arm_cpu.hh"
|
|
#include "cpu/kvm/base.hh"
|
|
#include "debug/Kvm.hh"
|
|
#include "debug/KvmContext.hh"
|
|
#include "debug/KvmInt.hh"
|
|
#include "sim/pseudo_inst.hh"
|
|
|
|
using namespace ArmISA;
|
|
|
|
#define EXTRACT_FIELD(val, mask, shift) \
|
|
(((val) & (mask)) >> (shift))
|
|
|
|
#define REG_IS_ARM(id) \
|
|
(((id) & KVM_REG_ARCH_MASK) == KVM_REG_ARM)
|
|
|
|
#define REG_IS_32BIT(id) \
|
|
(((id) & KVM_REG_SIZE_MASK) == KVM_REG_SIZE_U32)
|
|
|
|
#define REG_IS_64BIT(id) \
|
|
(((id) & KVM_REG_SIZE_MASK) == KVM_REG_SIZE_U64)
|
|
|
|
#define REG_IS_CP(id, cp) \
|
|
(((id) & KVM_REG_ARM_COPROC_MASK) == (cp))
|
|
|
|
#define REG_IS_CORE(id) REG_IS_CP((id), KVM_REG_ARM_CORE)
|
|
|
|
#define REG_IS_VFP(id) REG_IS_CP((id), KVM_REG_ARM_VFP)
|
|
#define REG_VFP_REG(id) ((id) & KVM_REG_ARM_VFP_MASK)
|
|
// HACK: These aren't really defined in any of the headers, so we'll
|
|
// assume some reasonable values for now.
|
|
#define REG_IS_VFP_REG(id) (REG_VFP_REG(id) < 0x100)
|
|
#define REG_IS_VFP_CTRL(id) (REG_VFP_REG(id) >= 0x100)
|
|
|
|
#define REG_IS_DEMUX(id) REG_IS_CP((id), KVM_REG_ARM_DEMUX)
|
|
|
|
|
|
// There is no constant in the kernel headers defining the mask to use
|
|
// to get the core register index. We'll just do what they do
|
|
// internally.
|
|
#define REG_CORE_IDX(id) \
|
|
(~(KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_ARM_CORE))
|
|
|
|
#define REG_CP(id) \
|
|
EXTRACT_FIELD(id, KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_COPROC_SHIFT)
|
|
|
|
#define REG_CRN(id) \
|
|
EXTRACT_FIELD(id, KVM_REG_ARM_32_CRN_MASK, KVM_REG_ARM_32_CRN_SHIFT)
|
|
|
|
#define REG_OPC1(id) \
|
|
EXTRACT_FIELD(id, KVM_REG_ARM_OPC1_MASK, KVM_REG_ARM_OPC1_SHIFT)
|
|
|
|
#define REG_CRM(id) \
|
|
EXTRACT_FIELD(id, KVM_REG_ARM_CRM_MASK, KVM_REG_ARM_CRM_SHIFT)
|
|
|
|
#define REG_OPC2(id) \
|
|
EXTRACT_FIELD(id, KVM_REG_ARM_32_OPC2_MASK, KVM_REG_ARM_32_OPC2_SHIFT)
|
|
|
|
#define REG_CP32(cpnum, crn, opc1, crm, opc2) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U32) | \
|
|
((cpnum) << KVM_REG_ARM_COPROC_SHIFT) | \
|
|
((crn) << KVM_REG_ARM_32_CRN_SHIFT) | \
|
|
((opc1) << KVM_REG_ARM_OPC1_SHIFT) | \
|
|
((crm) << KVM_REG_ARM_CRM_SHIFT) | \
|
|
((opc2) << KVM_REG_ARM_32_OPC2_SHIFT))
|
|
|
|
#define REG_CP64(cpnum, opc1, crm) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U64) | \
|
|
((cpnum) << KVM_REG_ARM_COPROC_SHIFT) | \
|
|
((opc1) << KVM_REG_ARM_OPC1_SHIFT) | \
|
|
((crm) << KVM_REG_ARM_CRM_SHIFT))
|
|
|
|
#define REG_CORE32(kname) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U32) | \
|
|
(KVM_REG_ARM_CORE) | \
|
|
(KVM_REG_ARM_CORE_REG(kname)))
|
|
|
|
#define REG_VFP32(regno) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U32) | \
|
|
KVM_REG_ARM_VFP | (regno))
|
|
|
|
#define REG_VFP64(regno) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U64) | \
|
|
KVM_REG_ARM_VFP | (regno))
|
|
|
|
#define REG_DEMUX32(dmxid, val) ( \
|
|
(KVM_REG_ARM | KVM_REG_SIZE_U32) | \
|
|
(dmxid) | (val))
|
|
|
|
// Some of the co-processor registers are invariants and must have the
|
|
// same value on both the host and the guest. We need to keep a list
|
|
// of these to prevent gem5 from fiddling with them on the guest.
|
|
static uint64_t invariant_reg_vector[] = {
|
|
REG_CP32(15, 0, 0, 0, 0), // MIDR
|
|
REG_CP32(15, 0, 0, 0, 1), // CTR
|
|
REG_CP32(15, 0, 0, 0, 2), // TCMTR
|
|
REG_CP32(15, 0, 0, 0, 3), // TLBTR
|
|
REG_CP32(15, 0, 0, 0, 6), // REVIDR
|
|
|
|
REG_CP32(15, 0, 0, 1, 0), // ID_PFR0
|
|
REG_CP32(15, 0, 0, 1, 1), // ID_PFR1
|
|
REG_CP32(15, 0, 0, 1, 2), // ID_DFR0
|
|
REG_CP32(15, 0, 0, 1, 3), // ID_AFR0
|
|
REG_CP32(15, 0, 0, 1, 4), // ID_MMFR0
|
|
REG_CP32(15, 0, 0, 1, 5), // ID_MMFR1
|
|
REG_CP32(15, 0, 0, 1, 6), // ID_MMFR2
|
|
REG_CP32(15, 0, 0, 1, 7), // ID_MMFR3
|
|
|
|
REG_CP32(15, 0, 0, 2, 0), // ID_ISAR0
|
|
REG_CP32(15, 0, 0, 2, 1), // ID_ISAR1
|
|
REG_CP32(15, 0, 0, 2, 2), // ID_ISAR2
|
|
REG_CP32(15, 0, 0, 2, 3), // ID_ISAR3
|
|
REG_CP32(15, 0, 0, 2, 4), // ID_ISAR4
|
|
REG_CP32(15, 0, 0, 2, 5), // ID_ISAR5
|
|
|
|
REG_CP32(15, 0, 1, 0, 0), // CSSIDR
|
|
REG_CP32(15, 0, 1, 0, 1), // CLIDR
|
|
REG_CP32(15, 0, 1, 0, 7), // AIDR
|
|
|
|
REG_VFP32(KVM_REG_ARM_VFP_MVFR0),
|
|
REG_VFP32(KVM_REG_ARM_VFP_MVFR1),
|
|
REG_VFP32(KVM_REG_ARM_VFP_FPSID),
|
|
|
|
REG_DEMUX32(KVM_REG_ARM_DEMUX_ID_CCSIDR, 0),
|
|
};
|
|
|
|
const static uint64_t KVM_REG64_TTBR0(REG_CP64(15, 0, 2));
|
|
const static uint64_t KVM_REG64_TTBR1(REG_CP64(15, 1, 2));
|
|
|
|
#define INTERRUPT_ID(type, vcpu, irq) ( \
|
|
((type) << KVM_ARM_IRQ_TYPE_SHIFT) | \
|
|
((vcpu) << KVM_ARM_IRQ_VCPU_SHIFT) | \
|
|
((irq) << KVM_ARM_IRQ_NUM_SHIFT))
|
|
|
|
#define INTERRUPT_VCPU_IRQ(vcpu) \
|
|
INTERRUPT_ID(KVM_ARM_IRQ_TYPE_CPU, vcpu, KVM_ARM_IRQ_CPU_IRQ)
|
|
|
|
#define INTERRUPT_VCPU_FIQ(vcpu) \
|
|
INTERRUPT_ID(KVM_ARM_IRQ_TYPE_CPU, vcpu, KVM_ARM_IRQ_CPU_FIQ)
|
|
|
|
|
|
#define COUNT_OF(l) (sizeof(l) / sizeof(*l))
|
|
|
|
const std::set<uint64_t> ArmKvmCPU::invariant_regs(
|
|
invariant_reg_vector,
|
|
invariant_reg_vector + COUNT_OF(invariant_reg_vector));
|
|
|
|
|
|
ArmKvmCPU::KvmIntRegInfo ArmKvmCPU::kvmIntRegs[] = {
|
|
{ REG_CORE32(usr_regs.ARM_r0), INTREG_R0, "R0" },
|
|
{ REG_CORE32(usr_regs.ARM_r1), INTREG_R1, "R1" },
|
|
{ REG_CORE32(usr_regs.ARM_r2), INTREG_R2, "R2" },
|
|
{ REG_CORE32(usr_regs.ARM_r3), INTREG_R3, "R3" },
|
|
{ REG_CORE32(usr_regs.ARM_r4), INTREG_R4, "R4" },
|
|
{ REG_CORE32(usr_regs.ARM_r5), INTREG_R5, "R5" },
|
|
{ REG_CORE32(usr_regs.ARM_r6), INTREG_R6, "R6" },
|
|
{ REG_CORE32(usr_regs.ARM_r7), INTREG_R7, "R7" },
|
|
{ REG_CORE32(usr_regs.ARM_r8), INTREG_R8, "R8" },
|
|
{ REG_CORE32(usr_regs.ARM_r9), INTREG_R9, "R9" },
|
|
{ REG_CORE32(usr_regs.ARM_r10), INTREG_R10, "R10" },
|
|
{ REG_CORE32(usr_regs.ARM_fp), INTREG_R11, "R11" },
|
|
{ REG_CORE32(usr_regs.ARM_ip), INTREG_R12, "R12" },
|
|
{ REG_CORE32(usr_regs.ARM_sp), INTREG_R13, "R13(USR)" },
|
|
{ REG_CORE32(usr_regs.ARM_lr), INTREG_R14, "R14(USR)" },
|
|
|
|
{ REG_CORE32(svc_regs[0]), INTREG_SP_SVC, "R13(SVC)" },
|
|
{ REG_CORE32(svc_regs[1]), INTREG_LR_SVC, "R14(SVC)" },
|
|
|
|
{ REG_CORE32(abt_regs[0]), INTREG_SP_ABT, "R13(ABT)" },
|
|
{ REG_CORE32(abt_regs[1]), INTREG_LR_ABT, "R14(ABT)" },
|
|
|
|
{ REG_CORE32(und_regs[0]), INTREG_SP_UND, "R13(UND)" },
|
|
{ REG_CORE32(und_regs[1]), INTREG_LR_UND, "R14(UND)" },
|
|
|
|
{ REG_CORE32(irq_regs[0]), INTREG_SP_IRQ, "R13(IRQ)" },
|
|
{ REG_CORE32(irq_regs[1]), INTREG_LR_IRQ, "R14(IRQ)" },
|
|
|
|
|
|
{ REG_CORE32(fiq_regs[0]), INTREG_R8_FIQ, "R8(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[1]), INTREG_R9_FIQ, "R9(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[2]), INTREG_R10_FIQ, "R10(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[3]), INTREG_R11_FIQ, "R11(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[4]), INTREG_R12_FIQ, "R12(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[5]), INTREG_R13_FIQ, "R13(FIQ)" },
|
|
{ REG_CORE32(fiq_regs[6]), INTREG_R14_FIQ, "R14(FIQ)" },
|
|
{ 0, NUM_INTREGS, NULL }
|
|
};
|
|
|
|
ArmKvmCPU::KvmCoreMiscRegInfo ArmKvmCPU::kvmCoreMiscRegs[] = {
|
|
{ REG_CORE32(usr_regs.ARM_cpsr), MISCREG_CPSR, "CPSR" },
|
|
{ REG_CORE32(svc_regs[2]), MISCREG_SPSR_SVC, "SPSR(SVC)" },
|
|
{ REG_CORE32(abt_regs[2]), MISCREG_SPSR_ABT, "SPSR(ABT)" },
|
|
{ REG_CORE32(und_regs[2]), MISCREG_SPSR_UND, "SPSR(UND)" },
|
|
{ REG_CORE32(irq_regs[2]), MISCREG_SPSR_IRQ, "SPSR(IRQ)" },
|
|
{ REG_CORE32(fiq_regs[2]), MISCREG_SPSR_FIQ, "SPSR(FIQ)" },
|
|
{ 0, NUM_MISCREGS }
|
|
};
|
|
|
|
ArmKvmCPU::ArmKvmCPU(ArmKvmCPUParams *params)
|
|
: BaseKvmCPU(params),
|
|
irqAsserted(false), fiqAsserted(false)
|
|
{
|
|
}
|
|
|
|
ArmKvmCPU::~ArmKvmCPU()
|
|
{
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::startup()
|
|
{
|
|
BaseKvmCPU::startup();
|
|
|
|
/* TODO: This needs to be moved when we start to support VMs with
|
|
* multiple threads since kvmArmVCpuInit requires that all CPUs in
|
|
* the VM have been created.
|
|
*/
|
|
/* TODO: The CPU type needs to be configurable once KVM on ARM
|
|
* starts to support more CPUs.
|
|
*/
|
|
kvmArmVCpuInit(KVM_ARM_TARGET_CORTEX_A15);
|
|
}
|
|
|
|
Tick
|
|
ArmKvmCPU::kvmRun(Tick ticks)
|
|
{
|
|
bool simFIQ(interrupts->checkRaw(INT_FIQ));
|
|
bool simIRQ(interrupts->checkRaw(INT_IRQ));
|
|
|
|
if (fiqAsserted != simFIQ) {
|
|
fiqAsserted = simFIQ;
|
|
DPRINTF(KvmInt, "KVM: Update FIQ state: %i\n", simFIQ);
|
|
vm.setIRQLine(INTERRUPT_VCPU_FIQ(vcpuID), simFIQ);
|
|
}
|
|
if (irqAsserted != simIRQ) {
|
|
irqAsserted = simIRQ;
|
|
DPRINTF(KvmInt, "KVM: Update IRQ state: %i\n", simIRQ);
|
|
vm.setIRQLine(INTERRUPT_VCPU_IRQ(vcpuID), simIRQ);
|
|
}
|
|
|
|
return BaseKvmCPU::kvmRun(ticks);
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::dump()
|
|
{
|
|
dumpKvmStateCore();
|
|
dumpKvmStateMisc();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateKvmState()
|
|
{
|
|
DPRINTF(KvmContext, "Updating KVM state...\n");
|
|
|
|
updateKvmStateCore();
|
|
updateKvmStateMisc();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateThreadContext()
|
|
{
|
|
DPRINTF(KvmContext, "Updating gem5 state...\n");
|
|
|
|
updateTCStateCore();
|
|
updateTCStateMisc();
|
|
}
|
|
|
|
Tick
|
|
ArmKvmCPU::onKvmExitHypercall()
|
|
{
|
|
ThreadContext *tc(getContext(0));
|
|
const uint32_t reg_ip(tc->readIntRegFlat(INTREG_R12));
|
|
const uint8_t func((reg_ip >> 8) & 0xFF);
|
|
const uint8_t subfunc(reg_ip & 0xFF);
|
|
|
|
DPRINTF(Kvm, "KVM Hypercall: 0x%x/0x%x\n", func, subfunc);
|
|
const uint64_t ret(PseudoInst::pseudoInst(getContext(0), func, subfunc));
|
|
|
|
// Just set the return value using the KVM API instead of messing
|
|
// with the context. We could have used the context, but that
|
|
// would have required us to request a full context sync.
|
|
setOneReg(REG_CORE32(usr_regs.ARM_r0), ret & 0xFFFFFFFF);
|
|
setOneReg(REG_CORE32(usr_regs.ARM_r1), (ret >> 32) & 0xFFFFFFFF);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const ArmKvmCPU::RegIndexVector &
|
|
ArmKvmCPU::getRegList() const
|
|
{
|
|
if (_regIndexList.size() == 0) {
|
|
std::unique_ptr<struct kvm_reg_list> regs;
|
|
uint64_t i(1);
|
|
|
|
do {
|
|
i <<= 1;
|
|
regs.reset((struct kvm_reg_list *)
|
|
operator new(sizeof(struct kvm_reg_list) +
|
|
i * sizeof(uint64_t)));
|
|
regs->n = i;
|
|
} while (!getRegList(*regs));
|
|
_regIndexList.assign(regs->reg,
|
|
regs->reg + regs->n);
|
|
}
|
|
|
|
return _regIndexList;
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::kvmArmVCpuInit(uint32_t target)
|
|
{
|
|
struct kvm_vcpu_init init;
|
|
|
|
memset(&init, 0, sizeof(init));
|
|
|
|
init.target = target;
|
|
|
|
kvmArmVCpuInit(init);
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::kvmArmVCpuInit(const struct kvm_vcpu_init &init)
|
|
{
|
|
if (ioctl(KVM_ARM_VCPU_INIT, (void *)&init) == -1)
|
|
panic("KVM: Failed to initialize vCPU\n");
|
|
}
|
|
|
|
MiscRegIndex
|
|
ArmKvmCPU::decodeCoProcReg(uint64_t id) const
|
|
{
|
|
const unsigned cp(REG_CP(id));
|
|
const bool is_reg32(REG_IS_32BIT(id));
|
|
const bool is_reg64(REG_IS_64BIT(id));
|
|
|
|
// CP numbers larger than 15 are reserved for KVM extensions
|
|
if (cp > 15)
|
|
return NUM_MISCREGS;
|
|
|
|
const unsigned crm(REG_CRM(id));
|
|
const unsigned crn(REG_CRN(id));
|
|
const unsigned opc1(REG_OPC1(id));
|
|
const unsigned opc2(REG_OPC2(id));
|
|
|
|
if (is_reg32) {
|
|
switch (cp) {
|
|
case 14:
|
|
return decodeCP14Reg(crn, opc1, crm, opc2);
|
|
|
|
case 15:
|
|
return decodeCP15Reg(crn, opc1, crm, opc2);
|
|
|
|
default:
|
|
return NUM_MISCREGS;
|
|
}
|
|
} else if(is_reg64) {
|
|
return NUM_MISCREGS;
|
|
} else {
|
|
warn("Unhandled register length, register (0x%x) ignored.\n");
|
|
return NUM_MISCREGS;
|
|
}
|
|
}
|
|
|
|
ArmISA::MiscRegIndex
|
|
ArmKvmCPU::decodeVFPCtrlReg(uint64_t id) const
|
|
{
|
|
if (!REG_IS_ARM(id) || !REG_IS_VFP(id) || !REG_IS_VFP_CTRL(id))
|
|
return NUM_MISCREGS;
|
|
|
|
const unsigned vfp_reg(REG_VFP_REG(id));
|
|
switch (vfp_reg) {
|
|
case KVM_REG_ARM_VFP_FPSID: return MISCREG_FPSID;
|
|
case KVM_REG_ARM_VFP_FPSCR: return MISCREG_FPSCR;
|
|
case KVM_REG_ARM_VFP_MVFR0: return MISCREG_MVFR0;
|
|
case KVM_REG_ARM_VFP_MVFR1: return MISCREG_MVFR1;
|
|
case KVM_REG_ARM_VFP_FPEXC: return MISCREG_FPEXC;
|
|
|
|
case KVM_REG_ARM_VFP_FPINST:
|
|
case KVM_REG_ARM_VFP_FPINST2:
|
|
warn_once("KVM: FPINST not implemented.\n");
|
|
return NUM_MISCREGS;
|
|
|
|
default:
|
|
return NUM_MISCREGS;
|
|
}
|
|
}
|
|
|
|
bool
|
|
ArmKvmCPU::isInvariantReg(uint64_t id)
|
|
{
|
|
/* Mask away the value field from multiplexed registers, we assume
|
|
* that entire groups of multiplexed registers can be treated as
|
|
* invariant. */
|
|
if (REG_IS_ARM(id) && REG_IS_DEMUX(id))
|
|
id &= ~KVM_REG_ARM_DEMUX_VAL_MASK;
|
|
|
|
return invariant_regs.find(id) != invariant_regs.end();
|
|
}
|
|
|
|
bool
|
|
ArmKvmCPU::getRegList(struct kvm_reg_list ®s) const
|
|
{
|
|
if (ioctl(KVM_GET_REG_LIST, (void *)®s) == -1) {
|
|
if (errno == E2BIG) {
|
|
return false;
|
|
} else {
|
|
panic("KVM: Failed to get vCPU register list (errno: %i)\n",
|
|
errno);
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::dumpKvmStateCore()
|
|
{
|
|
/* Print core registers */
|
|
uint32_t pc(getOneRegU32(REG_CORE32(usr_regs.ARM_pc)));
|
|
inform("PC: 0x%x\n", pc);
|
|
|
|
for (const KvmIntRegInfo *ri(kvmIntRegs);
|
|
ri->idx != NUM_INTREGS; ++ri) {
|
|
|
|
uint32_t value(getOneRegU32(ri->id));
|
|
inform("%s: 0x%x\n", ri->name, value);
|
|
}
|
|
|
|
for (const KvmCoreMiscRegInfo *ri(kvmCoreMiscRegs);
|
|
ri->idx != NUM_MISCREGS; ++ri) {
|
|
|
|
uint32_t value(getOneRegU32(ri->id));
|
|
inform("%s: 0x%x\n", miscRegName[ri->idx], value);
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::dumpKvmStateMisc()
|
|
{
|
|
/* Print co-processor registers */
|
|
const RegIndexVector ®_ids(getRegList());;
|
|
for (RegIndexVector::const_iterator it(reg_ids.begin());
|
|
it != reg_ids.end(); ++it) {
|
|
uint64_t id(*it);
|
|
|
|
if (REG_IS_ARM(id) && REG_CP(id) <= 15) {
|
|
dumpKvmStateCoProc(id);
|
|
} else if (REG_IS_ARM(id) && REG_IS_VFP(id)) {
|
|
dumpKvmStateVFP(id);
|
|
} else if (REG_IS_ARM(id) && REG_IS_DEMUX(id)) {
|
|
switch (id & KVM_REG_ARM_DEMUX_ID_MASK) {
|
|
case KVM_REG_ARM_DEMUX_ID_CCSIDR:
|
|
inform("CCSIDR [0x%x]: %s\n",
|
|
EXTRACT_FIELD(id,
|
|
KVM_REG_ARM_DEMUX_VAL_MASK,
|
|
KVM_REG_ARM_DEMUX_VAL_SHIFT),
|
|
getAndFormatOneReg(id));
|
|
break;
|
|
default:
|
|
inform("DEMUX [0x%x, 0x%x]: %s\n",
|
|
EXTRACT_FIELD(id,
|
|
KVM_REG_ARM_DEMUX_ID_MASK,
|
|
KVM_REG_ARM_DEMUX_ID_SHIFT),
|
|
EXTRACT_FIELD(id,
|
|
KVM_REG_ARM_DEMUX_VAL_MASK,
|
|
KVM_REG_ARM_DEMUX_VAL_SHIFT),
|
|
getAndFormatOneReg(id));
|
|
break;
|
|
}
|
|
} else if (!REG_IS_CORE(id)) {
|
|
inform("0x%x: %s\n", id, getAndFormatOneReg(id));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::dumpKvmStateCoProc(uint64_t id)
|
|
{
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_CP(id) <= 15);
|
|
|
|
if (REG_IS_32BIT(id)) {
|
|
// 32-bit co-proc registers
|
|
MiscRegIndex idx(decodeCoProcReg(id));
|
|
uint32_t value(getOneRegU32(id));
|
|
|
|
if (idx != NUM_MISCREGS &&
|
|
!(idx >= MISCREG_CP15_UNIMP_START && idx < MISCREG_CP15_END)) {
|
|
const char *name(miscRegName[idx]);
|
|
const unsigned m5_ne(tc->readMiscRegNoEffect(idx));
|
|
const unsigned m5_e(tc->readMiscReg(idx));
|
|
inform("CP%i: [CRn: c%i opc1: %.2i CRm: c%i opc2: %i inv: %i]: "
|
|
"[%s]: 0x%x/0x%x\n",
|
|
REG_CP(id), REG_CRN(id), REG_OPC1(id), REG_CRM(id),
|
|
REG_OPC2(id), isInvariantReg(id),
|
|
name, value, m5_e);
|
|
if (m5_e != m5_ne) {
|
|
inform("readMiscReg: %x, readMiscRegNoEffect: %x\n",
|
|
m5_e, m5_ne);
|
|
}
|
|
} else {
|
|
const char *name(idx != NUM_MISCREGS ? miscRegName[idx] : "-");
|
|
inform("CP%i: [CRn: c%i opc1: %.2i CRm: c%i opc2: %i inv: %i]: [%s]: "
|
|
"0x%x\n",
|
|
REG_CP(id), REG_CRN(id), REG_OPC1(id), REG_CRM(id),
|
|
REG_OPC2(id), isInvariantReg(id), name, value);
|
|
}
|
|
} else {
|
|
inform("CP%i: [CRn: c%i opc1: %.2i CRm: c%i opc2: %i inv: %i "
|
|
"len: 0x%x]: %s\n",
|
|
REG_CP(id), REG_CRN(id), REG_OPC1(id), REG_CRM(id),
|
|
REG_OPC2(id), isInvariantReg(id),
|
|
EXTRACT_FIELD(id, KVM_REG_SIZE_MASK, KVM_REG_SIZE_SHIFT),
|
|
getAndFormatOneReg(id));
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::dumpKvmStateVFP(uint64_t id)
|
|
{
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_IS_VFP(id));
|
|
|
|
if (REG_IS_VFP_REG(id)) {
|
|
const unsigned idx(id & KVM_REG_ARM_VFP_MASK);
|
|
inform("VFP reg %i: %s", idx, getAndFormatOneReg(id));
|
|
} else if (REG_IS_VFP_CTRL(id)) {
|
|
MiscRegIndex idx(decodeVFPCtrlReg(id));
|
|
if (idx != NUM_MISCREGS) {
|
|
inform("VFP [%s]: %s", miscRegName[idx], getAndFormatOneReg(id));
|
|
} else {
|
|
inform("VFP [0x%x]: %s", id, getAndFormatOneReg(id));
|
|
}
|
|
} else {
|
|
inform("VFP [0x%x]: %s", id, getAndFormatOneReg(id));
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateKvmStateCore()
|
|
{
|
|
for (const KvmIntRegInfo *ri(kvmIntRegs);
|
|
ri->idx != NUM_INTREGS; ++ri) {
|
|
|
|
uint64_t value(tc->readIntRegFlat(ri->idx));
|
|
DPRINTF(KvmContext, "kvm(%s) := 0x%x\n", ri->name, value);
|
|
setOneReg(ri->id, value);
|
|
}
|
|
|
|
DPRINTF(KvmContext, "kvm(PC) := 0x%x\n", tc->instAddr());
|
|
setOneReg(REG_CORE32(usr_regs.ARM_pc), tc->instAddr());
|
|
|
|
for (const KvmCoreMiscRegInfo *ri(kvmCoreMiscRegs);
|
|
ri->idx != NUM_MISCREGS; ++ri) {
|
|
|
|
uint64_t value(tc->readMiscReg(ri->idx));
|
|
DPRINTF(KvmContext, "kvm(%s) := 0x%x\n", ri->name, value);
|
|
setOneReg(ri->id, value);
|
|
}
|
|
|
|
if (DTRACE(KvmContext))
|
|
dumpKvmStateCore();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateKvmStateMisc()
|
|
{
|
|
static bool warned(false); // We can't use warn_once since we want
|
|
// to show /all/ registers
|
|
|
|
const RegIndexVector ®s(getRegList());
|
|
|
|
for (RegIndexVector::const_iterator it(regs.begin());
|
|
it != regs.end();
|
|
++it) {
|
|
|
|
if (!REG_IS_ARM(*it)) {
|
|
if (!warned)
|
|
warn("Skipping non-ARM register: 0x%x\n", *it);
|
|
} else if (isInvariantReg(*it)) {
|
|
DPRINTF(Kvm, "Skipping invariant register: 0x%x\n", *it);
|
|
} else if (REG_IS_CORE(*it)) {
|
|
// Core registers are handled in updateKvmStateCore
|
|
continue;
|
|
} else if (REG_CP(*it) <= 15) {
|
|
updateKvmStateCoProc(*it, !warned);
|
|
} else if (REG_IS_VFP(*it)) {
|
|
updateKvmStateVFP(*it, !warned);
|
|
} else {
|
|
if (!warned) {
|
|
warn("Skipping register with unknown CP (%i) id: 0x%x\n",
|
|
REG_CP(*it), *it);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
warned = true;
|
|
if (DTRACE(KvmContext))
|
|
dumpKvmStateMisc();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateKvmStateCoProc(uint64_t id, bool show_warnings)
|
|
{
|
|
MiscRegIndex reg(decodeCoProcReg(id));
|
|
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_CP(id) <= 15);
|
|
|
|
if (id == KVM_REG64_TTBR0 || id == KVM_REG64_TTBR1) {
|
|
// HACK HACK HACK: Workaround for 64-bit TTBRx
|
|
reg = (id == KVM_REG64_TTBR0 ? MISCREG_TTBR0 : MISCREG_TTBR1);
|
|
if (show_warnings)
|
|
hack("KVM: 64-bit TTBBRx workaround\n");
|
|
}
|
|
|
|
if (reg == NUM_MISCREGS) {
|
|
if (show_warnings) {
|
|
warn("KVM: Ignoring unknown KVM co-processor register (0x%.8x):\n",
|
|
id);
|
|
warn("\t0x%x: [CP: %i 64: %i CRn: c%i opc1: %.2i CRm: c%i"
|
|
" opc2: %i]\n",
|
|
id, REG_CP(id), REG_IS_64BIT(id), REG_CRN(id),
|
|
REG_OPC1(id), REG_CRM(id), REG_OPC2(id));
|
|
}
|
|
} else if (reg >= MISCREG_CP15_UNIMP_START && reg < MISCREG_CP15_END) {
|
|
if (show_warnings)
|
|
warn("KVM: Co-processor reg. %s not implemented by gem5.\n",
|
|
miscRegName[reg]);
|
|
} else {
|
|
setOneReg(id, tc->readMiscRegNoEffect(reg));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ArmKvmCPU::updateKvmStateVFP(uint64_t id, bool show_warnings)
|
|
{
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_IS_VFP(id));
|
|
|
|
if (REG_IS_VFP_REG(id)) {
|
|
if (!REG_IS_64BIT(id)) {
|
|
if (show_warnings)
|
|
warn("Unexpected VFP register length (reg: 0x%x).\n", id);
|
|
return;
|
|
}
|
|
const unsigned idx(id & KVM_REG_ARM_VFP_MASK);
|
|
const unsigned idx_base(idx << 1);
|
|
const unsigned idx_hi(idx_base + 1);
|
|
const unsigned idx_lo(idx_base + 0);
|
|
uint64_t value(
|
|
((uint64_t)tc->readFloatRegBitsFlat(idx_hi) << 32) |
|
|
tc->readFloatRegBitsFlat(idx_lo));
|
|
|
|
setOneReg(id, value);
|
|
} else if (REG_IS_VFP_CTRL(id)) {
|
|
MiscRegIndex idx(decodeVFPCtrlReg(id));
|
|
if (idx == NUM_MISCREGS) {
|
|
if (show_warnings)
|
|
warn("Unhandled VFP control register: 0x%x\n", id);
|
|
return;
|
|
}
|
|
if (!REG_IS_32BIT(id)) {
|
|
if (show_warnings)
|
|
warn("Ignoring VFP control register (%s) with "
|
|
"unexpected size.\n",
|
|
miscRegName[idx]);
|
|
return;
|
|
}
|
|
setOneReg(id, (uint32_t)tc->readMiscReg(idx));
|
|
} else {
|
|
if (show_warnings)
|
|
warn("Unhandled VFP register: 0x%x\n", id);
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateTCStateCore()
|
|
{
|
|
for (const KvmIntRegInfo *ri(kvmIntRegs);
|
|
ri->idx != NUM_INTREGS; ++ri) {
|
|
|
|
tc->setIntRegFlat(ri->idx, getOneRegU32(ri->id));
|
|
}
|
|
|
|
for (const KvmCoreMiscRegInfo *ri(kvmCoreMiscRegs);
|
|
ri->idx != NUM_MISCREGS; ++ri) {
|
|
|
|
tc->setMiscRegNoEffect(ri->idx, getOneRegU32(ri->id));
|
|
}
|
|
|
|
/* We want the simulator to execute all side-effects of the CPSR
|
|
* update since this updates PC state and register maps.
|
|
*/
|
|
tc->setMiscReg(MISCREG_CPSR, tc->readMiscRegNoEffect(MISCREG_CPSR));
|
|
|
|
// We update the PC state after we have updated the CPSR the
|
|
// contents of the CPSR affects how the npc is updated.
|
|
PCState pc(tc->pcState());
|
|
pc.set(getOneRegU32(REG_CORE32(usr_regs.ARM_pc)));
|
|
tc->pcState(pc);
|
|
|
|
if (DTRACE(KvmContext))
|
|
dumpKvmStateCore();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateTCStateMisc()
|
|
{
|
|
static bool warned(false); // We can't use warn_once since we want
|
|
// to show /all/ registers
|
|
|
|
const RegIndexVector ®_ids(getRegList());;
|
|
for (RegIndexVector::const_iterator it(reg_ids.begin());
|
|
it != reg_ids.end(); ++it) {
|
|
|
|
if (!REG_IS_ARM(*it)) {
|
|
if (!warned)
|
|
warn("Skipping non-ARM register: 0x%x\n", *it);
|
|
} else if (REG_IS_CORE(*it)) {
|
|
// Core registers are handled in updateKvmStateCore
|
|
} else if (REG_CP(*it) <= 15) {
|
|
updateTCStateCoProc(*it, !warned);
|
|
} else if (REG_IS_VFP(*it)) {
|
|
updateTCStateVFP(*it, !warned);
|
|
} else {
|
|
if (!warned) {
|
|
warn("Skipping register with unknown CP (%i) id: 0x%x\n",
|
|
REG_CP(*it), *it);
|
|
}
|
|
}
|
|
}
|
|
|
|
warned = true;
|
|
|
|
if (DTRACE(KvmContext))
|
|
dumpKvmStateMisc();
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateTCStateCoProc(uint64_t id, bool show_warnings)
|
|
{
|
|
MiscRegIndex reg(decodeCoProcReg(id));
|
|
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_CP(id) <= 15);
|
|
|
|
if (id == KVM_REG64_TTBR0 || id == KVM_REG64_TTBR1) {
|
|
// HACK HACK HACK: We don't currently support 64-bit TTBR0/TTBR1
|
|
hack_once("KVM: 64-bit TTBRx workaround\n");
|
|
tc->setMiscRegNoEffect(
|
|
id == KVM_REG64_TTBR0 ? MISCREG_TTBR0 : MISCREG_TTBR1,
|
|
(uint32_t)(getOneRegU64(id) & 0xFFFFFFFF));
|
|
} else if (reg == MISCREG_TTBCR) {
|
|
uint32_t value(getOneRegU64(id));
|
|
if (value & 0x80000000)
|
|
panic("KVM: Guest tried to enable LPAE.\n");
|
|
tc->setMiscRegNoEffect(reg, value);
|
|
} else if (reg == NUM_MISCREGS) {
|
|
if (show_warnings) {
|
|
warn("KVM: Ignoring unknown KVM co-processor register:\n", id);
|
|
warn("\t0x%x: [CP: %i 64: %i CRn: c%i opc1: %.2i CRm: c%i"
|
|
" opc2: %i]\n",
|
|
id, REG_CP(id), REG_IS_64BIT(id), REG_CRN(id),
|
|
REG_OPC1(id), REG_CRM(id), REG_OPC2(id));
|
|
}
|
|
} else if (reg >= MISCREG_CP15_UNIMP_START && reg < MISCREG_CP15_END) {
|
|
if (show_warnings)
|
|
warn_once("KVM: Co-processor reg. %s not implemented by gem5.\n",
|
|
miscRegName[reg]);
|
|
} else {
|
|
tc->setMiscRegNoEffect(reg, getOneRegU32(id));
|
|
}
|
|
}
|
|
|
|
void
|
|
ArmKvmCPU::updateTCStateVFP(uint64_t id, bool show_warnings)
|
|
{
|
|
assert(REG_IS_ARM(id));
|
|
assert(REG_IS_VFP(id));
|
|
|
|
if (REG_IS_VFP_REG(id)) {
|
|
if (!REG_IS_64BIT(id)) {
|
|
if (show_warnings)
|
|
warn("Unexpected VFP register length (reg: 0x%x).\n", id);
|
|
return;
|
|
}
|
|
const unsigned idx(id & KVM_REG_ARM_VFP_MASK);
|
|
const unsigned idx_base(idx << 1);
|
|
const unsigned idx_hi(idx_base + 1);
|
|
const unsigned idx_lo(idx_base + 0);
|
|
uint64_t value(getOneRegU64(id));
|
|
|
|
tc->setFloatRegBitsFlat(idx_hi, (value >> 32) & 0xFFFFFFFF);
|
|
tc->setFloatRegBitsFlat(idx_lo, value & 0xFFFFFFFF);
|
|
} else if (REG_IS_VFP_CTRL(id)) {
|
|
MiscRegIndex idx(decodeVFPCtrlReg(id));
|
|
if (idx == NUM_MISCREGS) {
|
|
if (show_warnings)
|
|
warn("Unhandled VFP control register: 0x%x\n", id);
|
|
return;
|
|
}
|
|
if (!REG_IS_32BIT(id)) {
|
|
if (show_warnings)
|
|
warn("Ignoring VFP control register (%s) with "
|
|
"unexpected size.\n",
|
|
miscRegName[idx]);
|
|
return;
|
|
}
|
|
tc->setMiscReg(idx, getOneRegU64(id));
|
|
} else {
|
|
if (show_warnings)
|
|
warn("Unhandled VFP register: 0x%x\n", id);
|
|
}
|
|
}
|
|
|
|
ArmKvmCPU *
|
|
ArmKvmCPUParams::create()
|
|
{
|
|
return new ArmKvmCPU(this);
|
|
}
|