kvm: FPU synchronization support on x86
authorAndreas Sandberg <andreas@sandberg.pp.se>
Mon, 30 Sep 2013 07:43:43 +0000 (09:43 +0200)
committerAndreas Sandberg <andreas@sandberg.pp.se>
Mon, 30 Sep 2013 07:43:43 +0000 (09:43 +0200)
This changeset adds support for synchronizing the FPU and SIMD state
of a virtual x86 CPU with gem5. It supports both the XSave API and the
KVM_(GET|SET)_FPU kernel API. The XSave interface can be disabled
using the useXSave parameter (in case of kernel
issues). Unfortunately, KVM_(GET|SET)_FPU interface seems to be buggy
in some kernels (specifically, the MXCSR register isn't always
synchronized), which means that it might not be possible to
synchronize MXCSR on old kernels without the XSave interface.

This changeset depends on the __float80 type in gcc and might not
build using llvm.

src/cpu/kvm/X86KvmCPU.py
src/cpu/kvm/x86_cpu.cc
src/cpu/kvm/x86_cpu.hh

index 0b12da67640a1655ab127720005961019089b44c..18a4d3d6f1066bcc338528eb2a298d58e3b075b1 100644 (file)
@@ -43,3 +43,5 @@ class X86KvmCPU(BaseKvmCPU):
       void dumpXSave();
       void dumpVCpuEvents();
 ''')
+
+    useXSave = Param.Bool(True, "Use XSave to synchronize FPU/SIMD registers")
index 1208bf2ec83773a406f03e415315fd068accf27e..8f7ca19e47033faf681e2782bc857bc721c8d4bb 100644 (file)
@@ -67,6 +67,38 @@ using namespace X86ISA;
 // data) is used to indicate that a segment has been accessed.
 #define SEG_TYPE_BIT_ACCESSED 1
 
+struct FXSave
+{
+    uint16_t fcw;
+    uint16_t fsw;
+    uint8_t ftwx;
+    uint8_t pad0;
+    uint16_t last_opcode;
+    union {
+        struct {
+            uint32_t fpu_ip;
+            uint16_t fpu_cs;
+            uint16_t pad1;
+            uint32_t fpu_dp;
+            uint16_t fpu_ds;
+            uint16_t pad2;
+        } ctrl32;
+
+        struct {
+            uint64_t fpu_ip;
+            uint64_t fpu_dp;
+        } ctrl64;
+    };
+    uint32_t mxcsr;
+    uint32_t mxcsr_mask;
+
+    uint8_t fpr[8][16];
+    uint8_t xmm[16][16];
+
+    uint64_t reserved[12];
+} M5_ATTR_PACKED;
+
+static_assert(sizeof(FXSave) == 512, "Unexpected size of FXSave");
 
 #define FOREACH_IREG()                          \
     do {                                        \
@@ -208,23 +240,61 @@ dumpKvm(const struct kvm_debugregs &regs)
 #endif
 
 static void
-dumpKvm(const struct kvm_fpu &fpu)
+dumpFpuSpec(const struct FXSave &xs)
 {
-    inform("FPU registers:\n");
+    inform("\tlast_ip: 0x%x\n", xs.ctrl64.fpu_ip);
+    inform("\tlast_dp: 0x%x\n", xs.ctrl64.fpu_dp);
+    inform("\tmxcsr_mask: 0x%x\n", xs.mxcsr_mask);
+}
+
+static void
+dumpFpuSpec(const struct kvm_fpu &fpu)
+{
+    inform("\tlast_ip: 0x%x\n", fpu.last_ip);
+    inform("\tlast_dp: 0x%x\n", fpu.last_dp);
+}
+
+template<typename T>
+static void
+dumpFpuCommon(const T &fpu)
+{
+    const unsigned top((fpu.fsw >> 11) & 0x7);
     inform("\tfcw: 0x%x\n", fpu.fcw);
-    inform("\tfsw: 0x%x\n", fpu.fsw);
+
+    inform("\tfsw: 0x%x (top: %i, "
+           "conditions: %s%s%s%s, exceptions: %s%s%s%s%s%s %s%s%s)\n",
+           fpu.fsw, top,
+
+           (fpu.fsw & CC0Bit) ? "C0" : "",
+           (fpu.fsw & CC1Bit) ? "C1" : "",
+           (fpu.fsw & CC2Bit) ? "C2" : "",
+           (fpu.fsw & CC3Bit) ? "C3" : "",
+
+           (fpu.fsw & IEBit) ? "I" : "",
+           (fpu.fsw & DEBit) ? "D" : "",
+           (fpu.fsw & ZEBit) ? "Z" : "",
+           (fpu.fsw & OEBit) ? "O" : "",
+           (fpu.fsw & UEBit) ? "U" : "",
+           (fpu.fsw & PEBit) ? "P" : "",
+
+           (fpu.fsw & StackFaultBit) ? "SF " : "",
+           (fpu.fsw & ErrSummaryBit) ? "ES " : "",
+           (fpu.fsw & BusyBit) ? "BUSY " : ""
+        );
     inform("\tftwx: 0x%x\n", fpu.ftwx);
     inform("\tlast_opcode: 0x%x\n", fpu.last_opcode);
-    inform("\tlast_ip: 0x%x\n", fpu.last_ip);
-    inform("\tlast_dp: 0x%x\n", fpu.last_dp);
+    dumpFpuSpec(fpu);
     inform("\tmxcsr: 0x%x\n", fpu.mxcsr);
     inform("\tFP Stack:\n");
     for (int i = 0; i < 8; ++i) {
-        const bool empty(!((fpu.ftwx >> i) & 0x1));
+        const unsigned reg_idx((i + top) & 0x7);
+        const bool empty(!((fpu.ftwx >> reg_idx) & 0x1));
+        const double value(X86ISA::loadFloat80(fpu.fpr[i]));
         char hex[33];
-        for (int j = 0; j < 16; ++j)
+        for (int j = 0; j < 10; ++j)
             snprintf(&hex[j*2], 3, "%.2x", fpu.fpr[i][j]);
-        inform("\t\t%i: 0x%s%s\n", i, hex, empty ? " (e)" : "");
+        inform("\t\tST%i/%i: 0x%s (%f)%s\n", i, reg_idx,
+               hex, value, empty ? " (e)" : "");
     }
     inform("\tXMM registers:\n");
     for (int i = 0; i < 16; ++i) {
@@ -235,6 +305,20 @@ dumpKvm(const struct kvm_fpu &fpu)
     }
 }
 
+static void
+dumpKvm(const struct kvm_fpu &fpu)
+{
+    inform("FPU registers:\n");
+    dumpFpuCommon(fpu);
+}
+
+static void
+dumpKvm(const struct kvm_xsave &xsave)
+{
+    inform("FPU registers (XSave):\n");
+    dumpFpuCommon(*(FXSave *)xsave.region);
+}
+
 static void
 dumpKvm(const struct kvm_msrs &msrs)
 {
@@ -260,15 +344,6 @@ dumpKvm(const struct kvm_xcrs &regs)
     }
 }
 
-static void
-dumpKvm(const struct kvm_xsave &xsave)
-{
-    inform("KVM XSAVE:\n");
-
-    Trace::dump((Tick)-1, "xsave.region",
-                xsave.region, sizeof(xsave.region));
-}
-
 static void
 dumpKvm(const struct kvm_vcpu_events &events)
 {
@@ -441,7 +516,8 @@ checkSeg(const char *name, const int idx, const struct kvm_segment &seg,
 }
 
 X86KvmCPU::X86KvmCPU(X86KvmCPUParams *params)
-    : BaseKvmCPU(params)
+    : BaseKvmCPU(params),
+      useXSave(params->useXSave)
 {
     Kvm &kvm(vm.kvm);
 
@@ -457,6 +533,14 @@ X86KvmCPU::X86KvmCPU(X86KvmCPUParams *params)
     haveDebugRegs = kvm.capDebugRegs();
     haveXSave = kvm.capXSave();
     haveXCRs = kvm.capXCRs();
+
+    if (useXSave && !haveXSave) {
+        warn("KVM: XSAVE not supported by host. MXCSR synchronization might be "
+             "unreliable due to kernel bugs.\n");
+        useXSave = false;
+    } else if (!useXSave) {
+        warn("KVM: XSave FPU/SIMD synchronization disabled by user.\n");
+    }
 }
 
 X86KvmCPU::~X86KvmCPU()
@@ -483,13 +567,15 @@ void
 X86KvmCPU::dump()
 {
     dumpIntRegs();
-    dumpFpuRegs();
+    if (useXSave)
+        dumpXSave();
+    else
+        dumpFpuRegs();
     dumpSpecRegs();
     dumpDebugRegs();
     dumpXCRs();
     dumpVCpuEvents();
     dumpMSRs();
-    dumpXSave();
 }
 
 void
@@ -729,10 +815,100 @@ X86KvmCPU::updateKvmStateSRegs()
 
     setSpecialRegisters(sregs);
 }
+
+template <typename T>
+static void
+updateKvmStateFPUCommon(ThreadContext *tc, T &fpu)
+{
+    static_assert(sizeof(X86ISA::FloatRegBits) == 8,
+                  "Unexpected size of X86ISA::FloatRegBits");
+
+    fpu.mxcsr = tc->readMiscRegNoEffect(MISCREG_MXCSR);
+    fpu.fcw = tc->readMiscRegNoEffect(MISCREG_FCW);
+    // No need to rebuild from MISCREG_FSW and MISCREG_TOP if we read
+    // with effects.
+    fpu.fsw = tc->readMiscReg(MISCREG_FSW);
+
+    uint64_t ftw(tc->readMiscRegNoEffect(MISCREG_FTW));
+    fpu.ftwx = X86ISA::convX87TagsToXTags(ftw);
+
+    fpu.last_opcode = tc->readMiscRegNoEffect(MISCREG_FOP);
+
+    const unsigned top((fpu.fsw >> 11) & 0x7);
+    for (int i = 0; i < 8; ++i) {
+        const unsigned reg_idx((i + top) & 0x7);
+        const double value(tc->readFloatReg(FLOATREG_FPR(reg_idx)));
+        DPRINTF(KvmContext, "Setting KVM FP reg %i (st[%i]) := %f\n",
+                reg_idx, i, value);
+        X86ISA::storeFloat80(fpu.fpr[i], value);
+    }
+
+    // TODO: We should update the MMX state
+
+    for (int i = 0; i < 16; ++i) {
+        *(X86ISA::FloatRegBits *)&fpu.xmm[i][0] =
+            tc->readFloatRegBits(FLOATREG_XMM_LOW(i));
+        *(X86ISA::FloatRegBits *)&fpu.xmm[i][8] =
+            tc->readFloatRegBits(FLOATREG_XMM_HIGH(i));
+    }
+}
+
+void
+X86KvmCPU::updateKvmStateFPULegacy()
+{
+    struct kvm_fpu fpu;
+
+    // There is some padding in the FP registers, so we'd better zero
+    // the whole struct.
+    memset(&fpu, 0, sizeof(fpu));
+
+    updateKvmStateFPUCommon(tc, fpu);
+
+    if (tc->readMiscRegNoEffect(MISCREG_FISEG))
+        warn_once("MISCREG_FISEG is non-zero.\n");
+
+    fpu.last_ip = tc->readMiscRegNoEffect(MISCREG_FIOFF);
+
+    if (tc->readMiscRegNoEffect(MISCREG_FOSEG))
+        warn_once("MISCREG_FOSEG is non-zero.\n");
+
+    fpu.last_dp = tc->readMiscRegNoEffect(MISCREG_FOOFF);
+
+    setFPUState(fpu);
+}
+
+void
+X86KvmCPU::updateKvmStateFPUXSave()
+{
+    struct kvm_xsave kxsave;
+    FXSave &xsave(*(FXSave *)kxsave.region);
+
+    // There is some padding and reserved fields in the structure, so
+    // we'd better zero the whole thing.
+    memset(&kxsave, 0, sizeof(kxsave));
+
+    updateKvmStateFPUCommon(tc, xsave);
+
+    if (tc->readMiscRegNoEffect(MISCREG_FISEG))
+        warn_once("MISCREG_FISEG is non-zero.\n");
+
+    xsave.ctrl64.fpu_ip = tc->readMiscRegNoEffect(MISCREG_FIOFF);
+
+    if (tc->readMiscRegNoEffect(MISCREG_FOSEG))
+        warn_once("MISCREG_FOSEG is non-zero.\n");
+
+    xsave.ctrl64.fpu_dp = tc->readMiscRegNoEffect(MISCREG_FOOFF);
+
+    setXSave(kxsave);
+}
+
 void
 X86KvmCPU::updateKvmStateFPU()
 {
-    warn_once("X86KvmCPU::updateKvmStateFPU not implemented\n");
+    if (useXSave)
+        updateKvmStateFPUXSave();
+    else
+        updateKvmStateFPULegacy();
 }
 
 void
@@ -766,7 +942,10 @@ X86KvmCPU::updateThreadContext()
 
     updateThreadContextRegs();
     updateThreadContextSRegs();
-    updateThreadContextFPU();
+    if (useXSave)
+        updateThreadContextXSave();
+    else
+        updateThreadContextFPU();
     updateThreadContextMSRs();
 
     // The M5 misc reg caches some values from other
@@ -851,10 +1030,72 @@ X86KvmCPU::updateThreadContextSRegs()
 #undef APPLY_DTABLE
 }
 
+template<typename T>
+static void
+updateThreadContextFPUCommon(ThreadContext *tc, const T &fpu)
+{
+    const unsigned top((fpu.fsw >> 11) & 0x7);
+
+    static_assert(sizeof(X86ISA::FloatRegBits) == 8,
+                  "Unexpected size of X86ISA::FloatRegBits");
+
+    for (int i = 0; i < 8; ++i) {
+        const unsigned reg_idx((i + top) & 0x7);
+        const double value(X86ISA::loadFloat80(fpu.fpr[i]));
+        DPRINTF(KvmContext, "Setting gem5 FP reg %i (st[%i]) := %f\n",
+                reg_idx, i, value);
+        tc->setFloatReg(FLOATREG_FPR(reg_idx), value);
+    }
+
+    // TODO: We should update the MMX state
+
+    tc->setMiscRegNoEffect(MISCREG_X87_TOP, top);
+    tc->setMiscRegNoEffect(MISCREG_MXCSR, fpu.mxcsr);
+    tc->setMiscRegNoEffect(MISCREG_FCW, fpu.fcw);
+    tc->setMiscRegNoEffect(MISCREG_FSW, fpu.fsw);
+
+    uint64_t ftw(convX87XTagsToTags(fpu.ftwx));
+    // TODO: Are these registers really the same?
+    tc->setMiscRegNoEffect(MISCREG_FTW, ftw);
+    tc->setMiscRegNoEffect(MISCREG_FTAG, ftw);
+
+    tc->setMiscRegNoEffect(MISCREG_FOP, fpu.last_opcode);
+
+    for (int i = 0; i < 16; ++i) {
+        tc->setFloatRegBits(FLOATREG_XMM_LOW(i),
+                            *(X86ISA::FloatRegBits *)&fpu.xmm[i][0]);
+        tc->setFloatRegBits(FLOATREG_XMM_HIGH(i),
+                            *(X86ISA::FloatRegBits *)&fpu.xmm[i][8]);
+    }
+}
+
 void
 X86KvmCPU::updateThreadContextFPU()
 {
-    warn_once("X86KvmCPU::updateThreadContextFPU not implemented\n");
+    struct kvm_fpu fpu;
+    getFPUState(fpu);
+
+    updateThreadContextFPUCommon(tc, fpu);
+
+    tc->setMiscRegNoEffect(MISCREG_FISEG, 0);
+    tc->setMiscRegNoEffect(MISCREG_FIOFF, fpu.last_ip);
+    tc->setMiscRegNoEffect(MISCREG_FOSEG, 0);
+    tc->setMiscRegNoEffect(MISCREG_FOOFF, fpu.last_dp);
+}
+
+void
+X86KvmCPU::updateThreadContextXSave()
+{
+    struct kvm_xsave kxsave;
+    FXSave &xsave(*(FXSave *)kxsave.region);
+    getXSave(kxsave);
+
+    updateThreadContextFPUCommon(tc, xsave);
+
+    tc->setMiscRegNoEffect(MISCREG_FISEG, 0);
+    tc->setMiscRegNoEffect(MISCREG_FIOFF, xsave.ctrl64.fpu_ip);
+    tc->setMiscRegNoEffect(MISCREG_FOSEG, 0);
+    tc->setMiscRegNoEffect(MISCREG_FOOFF, xsave.ctrl64.fpu_dp);
 }
 
 void
index 602fc8416da9e1009550e74f14995e3394d1f320..7dacdb03f39a1b47253b953d72347e78f43b1c88 100644 (file)
@@ -171,8 +171,33 @@ class X86KvmCPU : public BaseKvmCPU
     void updateKvmStateRegs();
     /** Update control registers (CRx, segments, etc.) */
     void updateKvmStateSRegs();
-    /** Update FPU and SIMD registers */
+    /**
+     * Update FPU and SIMD registers
+     *
+     * This method uses the appropriate (depending on availability and
+     * user configuration) kernel API by calling
+     * updateKvmStateFPULegacy() or updateKvmStateFPUXSave().
+     *
+     * @see updateKvmStateFPULegacy()
+     * @see updateKvmStateFPUXSave()
+     */
     void updateKvmStateFPU();
+    /**
+     * Update FPU and SIMD registers using the legacy API
+     *
+     * @note This method should normally only be called by
+     * updateKvmStateFPU() which automatically chooses between
+     * available APIs.
+     */
+    void updateKvmStateFPULegacy();
+    /**
+     * Update FPU and SIMD registers using the XSave API
+     *
+     * @note This method should normally only be called by
+     * updateKvmStateFPU() which automatically chooses between
+     * available APIs.
+     */
+    void updateKvmStateFPUXSave();
     /** Update MSR registers */
     void updateKvmStateMSRs();
     /** @} */
@@ -187,8 +212,10 @@ class X86KvmCPU : public BaseKvmCPU
     void updateThreadContextRegs();
     /** Update control registers (CRx, segments, etc.) */
     void updateThreadContextSRegs();
-    /** Update FPU and SIMD registers */
+    /** Update FPU and SIMD registers using the legacy API */
     void updateThreadContextFPU();
+    /** Update FPU and SIMD registers using the XSave API */
+    void updateThreadContextXSave();
     /** Update MSR registers */
     void updateThreadContextMSRs();
     /** @} */
@@ -217,6 +244,11 @@ class X86KvmCPU : public BaseKvmCPU
     bool haveDebugRegs;
     /** Kvm::capXSave() available? */
     bool haveXSave;
+    /**
+     * Should the XSave interface be used to sync the FPU and SIMD
+     * registers?
+     */
+    bool useXSave;
     /** Kvm::capXCRs() available? */
     bool haveXCRs;
     /** @} */