diff mbox

[8/9] mips/kvm: Support FPU in MIPS KVM guests

Message ID 1426087371-16166-9-git-send-email-james.hogan@imgtec.com
State New
Headers show

Commit Message

James Hogan March 11, 2015, 3:22 p.m. UTC
Support the new KVM_CAP_MIPS_FPU capability, which allows the host's FPU
to be exposed to the KVM guest.

The capability is enabled if the guest core has an FPU according to its
Config1 register. Various config bits are now writeable so that KVM is
aware of the configuration (Config1.FP) and so that QEMU can
save/restore the guest modifiable bits (Config5.FRE, Config5.UFR,
Config5.UFE). The FCSR/FIR registers and the floating point registers
are now saved/restored (depending on the FR mode bit).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Leon Alrae <leon.alrae@imgtec.com>
Cc: Aurelien Jarno <aurelien@aurel32.net>
---
 linux-headers/linux/kvm.h |   1 +
 target-mips/cpu.h         |   2 +
 target-mips/kvm.c         | 124 ++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 123 insertions(+), 4 deletions(-)

Comments

Paolo Bonzini March 12, 2015, 4:44 p.m. UTC | #1
On 11/03/2015 16:22, James Hogan wrote:
> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
> index 12045a11c036..410eb158f564 100644
> --- a/linux-headers/linux/kvm.h
> +++ b/linux-headers/linux/kvm.h
> @@ -761,6 +761,7 @@ struct kvm_ppc_smmu_info {
>  #define KVM_CAP_PPC_FIXUP_HCALL 103
>  #define KVM_CAP_PPC_ENABLE_HCALL 104
>  #define KVM_CAP_CHECK_EXTENSION_VM 105
> +#define KVM_CAP_MIPS_FPU 107
>  

Changes to linux-headers/linux/kvm.h should go in through a complete
sync of the kernel headers.  The good thing is that then the series
becomes 100% MIPS-specific. :)

Paolo
James Hogan March 12, 2015, 5 p.m. UTC | #2
On 12/03/15 16:44, Paolo Bonzini wrote:
> 
> 
> On 11/03/2015 16:22, James Hogan wrote:
>> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
>> index 12045a11c036..410eb158f564 100644
>> --- a/linux-headers/linux/kvm.h
>> +++ b/linux-headers/linux/kvm.h
>> @@ -761,6 +761,7 @@ struct kvm_ppc_smmu_info {
>>  #define KVM_CAP_PPC_FIXUP_HCALL 103
>>  #define KVM_CAP_PPC_ENABLE_HCALL 104
>>  #define KVM_CAP_CHECK_EXTENSION_VM 105
>> +#define KVM_CAP_MIPS_FPU 107
>>  
> 
> Changes to linux-headers/linux/kvm.h should go in through a complete
> sync of the kernel headers.  The good thing is that then the series
> becomes 100% MIPS-specific. :)

Right. I'll maybe move all those changes in a preliminary "don't apply
me" patch for reference (there are some other duplicated definitions in
kvm.c too, similar to what patch 1 cleans up) and assume that one way or
another a sync will happen between the KVM changes being merged and the
QEMU patchset being applied.

Cheers
James
diff mbox

Patch

diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index 12045a11c036..410eb158f564 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -761,6 +761,7 @@  struct kvm_ppc_smmu_info {
 #define KVM_CAP_PPC_FIXUP_HCALL 103
 #define KVM_CAP_PPC_ENABLE_HCALL 104
 #define KVM_CAP_CHECK_EXTENSION_VM 105
+#define KVM_CAP_MIPS_FPU 107
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
diff --git a/target-mips/cpu.h b/target-mips/cpu.h
index 5ea61bceea42..07c1e0146ec9 100644
--- a/target-mips/cpu.h
+++ b/target-mips/cpu.h
@@ -462,6 +462,8 @@  struct CPUMIPSState {
 #define CP0C5_CV         29
 #define CP0C5_EVA        28
 #define CP0C5_MSAEn      27
+#define CP0C5_UFE        9
+#define CP0C5_FRE        8
 #define CP0C5_SBRI       6
 #define CP0C5_UFR        2
 #define CP0C5_NFExists   0
diff --git a/target-mips/kvm.c b/target-mips/kvm.c
index 7f72d6fb511f..c0191a525171 100644
--- a/target-mips/kvm.c
+++ b/target-mips/kvm.c
@@ -29,6 +29,8 @@ 
 #define DPRINTF(fmt, ...) \
     do { if (DEBUG_KVM) { fprintf(stderr, fmt, ## __VA_ARGS__); } } while (0)
 
+static int kvm_mips_fpu_cap;
+
 const KVMCapabilityInfo kvm_arch_required_capabilities[] = {
     KVM_CAP_LAST_INFO
 };
@@ -45,16 +47,29 @@  int kvm_arch_init(KVMState *s)
     /* MIPS has 128 signals */
     kvm_set_sigmask_len(s, 16);
 
+    kvm_mips_fpu_cap = kvm_check_extension(s, KVM_CAP_MIPS_FPU);
+
     DPRINTF("%s\n", __func__);
     return 0;
 }
 
 int kvm_arch_init_vcpu(CPUState *cs)
 {
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
     int ret = 0;
 
     qemu_add_vm_change_state_handler(kvm_mips_update_state, cs);
 
+    if (kvm_mips_fpu_cap && env->CP0_Config1 & (1 << CP0C1_FP)) {
+        ret = kvm_vcpu_enable_cap(cs, KVM_CAP_MIPS_FPU, 0, 0);
+        if (ret < 0) {
+            /* mark unsupported so it gets disabled on reset */
+            kvm_mips_fpu_cap = 0;
+            ret = 0;
+        }
+    }
+
     DPRINTF("%s\n", __func__);
     return ret;
 }
@@ -63,8 +78,8 @@  void kvm_mips_reset_vcpu(MIPSCPU *cpu)
 {
     CPUMIPSState *env = &cpu->env;
 
-    if (env->CP0_Config1 & (1 << CP0C1_FP)) {
-        fprintf(stderr, "Warning: FPU not supported with KVM, disabling\n");
+    if (!kvm_mips_fpu_cap && env->CP0_Config1 & (1 << CP0C1_FP)) {
+        fprintf(stderr, "Warning: KVM does not support FPU, disabling\n");
         env->CP0_Config1 &= ~(1 << CP0C1_FP);
     }
 
@@ -386,11 +401,14 @@  static inline int kvm_mips_get_one_ureg64(CPUState *cs, uint64 reg_id,
 }
 
 #define KVM_REG_MIPS_CP0_CONFIG_MASK    (1 << CP0C0_M)
-#define KVM_REG_MIPS_CP0_CONFIG1_MASK   (1 << CP0C1_M)
+#define KVM_REG_MIPS_CP0_CONFIG1_MASK   ((1 << CP0C1_M) | \
+                                         (1 << CP0C1_FP))
 #define KVM_REG_MIPS_CP0_CONFIG2_MASK   (1 << CP0C2_M)
 #define KVM_REG_MIPS_CP0_CONFIG3_MASK   (1 << CP0C3_M)
 #define KVM_REG_MIPS_CP0_CONFIG4_MASK   (1 << CP0C4_M)
-#define KVM_REG_MIPS_CP0_CONFIG5_MASK   0
+#define KVM_REG_MIPS_CP0_CONFIG5_MASK   ((1 << CP0C5_UFE) | \
+                                         (1 << CP0C5_FRE) | \
+                                         (1 << CP0C5_UFR))
 
 static inline int kvm_mips_change_one_reg(CPUState *cs, uint64_t reg_id,
                                           int32_t *addr, int32_t mask)
@@ -552,6 +570,98 @@  static void kvm_mips_update_state(void *opaque, int running, RunState state)
     }
 }
 
+static int kvm_mips_put_fpu_registers(CPUState *cs, int level)
+{
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
+    int err, ret = 0;
+    unsigned int i;
+
+    /* Only put FPU state if we're emulating a CPU with an FPU */
+    if (env->CP0_Config1 & (1 << CP0C1_FP)) {
+        /* FPU Control Registers */
+        if (level == KVM_PUT_FULL_STATE) {
+            err = kvm_mips_put_one_ureg(cs, KVM_REG_MIPS_FCR_IR,
+                                        &env->active_fpu.fcr0);
+            if (err < 0) {
+                DPRINTF("%s: Failed to put FCR_IR (%d)\n", __func__, err);
+                ret = err;
+            }
+        }
+        err = kvm_mips_put_one_ureg(cs, KVM_REG_MIPS_FCR_CSR,
+                                    &env->active_fpu.fcr31);
+        if (err < 0) {
+            DPRINTF("%s: Failed to put FCR_CSR (%d)\n", __func__, err);
+            ret = err;
+        }
+
+        /* Floating point registers */
+        for (i = 0; i < 32; ++i) {
+            if (env->CP0_Status & (1 << CP0St_FR)) {
+                err = kvm_mips_put_one_ureg64(cs, KVM_REG_MIPS_FPR_64(i),
+                                              &env->active_fpu.fpr[i].d);
+            } else {
+                err = kvm_mips_get_one_ureg(cs, KVM_REG_MIPS_FPR_32(i),
+                                      &env->active_fpu.fpr[i].w[FP_ENDIAN_IDX]);
+            }
+            if (err < 0) {
+                DPRINTF("%s: Failed to put FPR%u (%d)\n", __func__, i, err);
+                ret = err;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int kvm_mips_get_fpu_registers(CPUState *cs)
+{
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
+    int err, ret = 0;
+    unsigned int i;
+
+    /* Only get FPU state if we're emulating a CPU with an FPU */
+    if (env->CP0_Config1 & (1 << CP0C1_FP)) {
+        /* FPU Control Registers */
+        err = kvm_mips_get_one_ureg(cs, KVM_REG_MIPS_FCR_IR,
+                                    &env->active_fpu.fcr0);
+        if (err < 0) {
+            DPRINTF("%s: Failed to get FCR_IR (%d)\n", __func__, err);
+            ret = err;
+        }
+        err = kvm_mips_get_one_ureg(cs, KVM_REG_MIPS_FCR_CSR,
+                                    &env->active_fpu.fcr31);
+        if (err < 0) {
+            DPRINTF("%s: Failed to get FCR_CSR (%d)\n", __func__, err);
+            ret = err;
+        } else {
+            /* set rounding mode */
+            restore_rounding_mode(env);
+            /* set flush-to-zero mode */
+            restore_flush_mode(env);
+        }
+
+        /* Floating point registers */
+        for (i = 0; i < 32; ++i) {
+            if (env->CP0_Status & (1 << CP0St_FR)) {
+                err = kvm_mips_get_one_ureg64(cs, KVM_REG_MIPS_FPR_64(i),
+                                              &env->active_fpu.fpr[i].d);
+            } else {
+                err = kvm_mips_get_one_ureg(cs, KVM_REG_MIPS_FPR_32(i),
+                                      &env->active_fpu.fpr[i].w[FP_ENDIAN_IDX]);
+            }
+            if (err < 0) {
+                DPRINTF("%s: Failed to get FPR%u (%d)\n", __func__, i, err);
+                ret = err;
+            }
+        }
+    }
+
+    return ret;
+}
+
+
 static int kvm_mips_put_cp0_registers(CPUState *cs, int level)
 {
     MIPSCPU *cpu = MIPS_CPU(cs);
@@ -838,6 +948,11 @@  int kvm_arch_put_registers(CPUState *cs, int level)
         return ret;
     }
 
+    ret = kvm_mips_put_fpu_registers(cs, level);
+    if (ret < 0) {
+        return ret;
+    }
+
     return ret;
 }
 
@@ -865,6 +980,7 @@  int kvm_arch_get_registers(CPUState *cs)
     env->active_tc.PC = regs.pc;
 
     kvm_mips_get_cp0_registers(cs);
+    kvm_mips_get_fpu_registers(cs);
 
     return ret;
 }