MIPS: Respect the ISA level in FCSR handling
authorMaciej W. Rozycki <macro@linux-mips.org>
Fri, 3 Apr 2015 22:27:48 +0000 (23:27 +0100)
committerRalf Baechle <ralf@linux-mips.org>
Tue, 7 Apr 2015 23:10:37 +0000 (01:10 +0200)
Define the central place the default FCSR value is set from, initialised
in `cpu_probe'.  Determine the FCSR mask applied to values written to
the register with CTC1 in the full emulation mode and via ptrace(2),
according to the ISA level of processor hardware or the writability of
bits 31:18 if actual FPU hardware is used.

Software may rely on FCSR bits whose functions our emulator does not
implement, so it should not allow them to be set or software may get
confused.  For ptrace(2) it's just sanity.

[ralf@linux-mips.org: Fixed double inclusion of <asm/current.h>.]

Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/9711/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/cpu-info.h
arch/mips/include/asm/elf.h
arch/mips/include/asm/fpu.h
arch/mips/include/asm/fpu_emulator.h
arch/mips/kernel/cpu-probe.c
arch/mips/kernel/ptrace.c
arch/mips/kernel/r2300_switch.S
arch/mips/kernel/r4k_switch.S
arch/mips/loongson/loongson-3/cop2-ex.c
arch/mips/math-emu/cp1emu.c

index c3f4f2d2e1088459b2aa10c6292e5de76665d5bc..e7dc785a91ca6c2e6f64c1dc5206a06a59ba80ac 100644 (file)
@@ -49,6 +49,8 @@ struct cpuinfo_mips {
        unsigned int            udelay_val;
        unsigned int            processor_id;
        unsigned int            fpu_id;
+       unsigned int            fpu_csr31;
+       unsigned int            fpu_msk31;
        unsigned int            msa_id;
        unsigned int            cputype;
        int                     isa_level;
index 535f196ffe02da7ad769ab0b7053b4dd5253dd82..612cf519bd889db63f9265eb2dde151f551ba7ac 100644 (file)
@@ -11,6 +11,9 @@
 #include <linux/fs.h>
 #include <uapi/linux/elf.h>
 
+#include <asm/cpu-info.h>
+#include <asm/current.h>
+
 /* ELF header e_flags defines. */
 /* MIPS architecture level. */
 #define EF_MIPS_ARCH_1         0x00000000      /* -mips1 code.  */
@@ -297,6 +300,8 @@ do {                                                                        \
        mips_set_personality_fp(state);                                 \
                                                                        \
        current->thread.abi = &mips_abi;                                \
+                                                                       \
+       current->thread.fpu.fcr31 = current_cpu_data.fpu_csr31;         \
 } while (0)
 
 #endif /* CONFIG_32BIT */
@@ -356,6 +361,8 @@ do {                                                                        \
        else                                                            \
                current->thread.abi = &mips_abi;                        \
                                                                        \
+       current->thread.fpu.fcr31 = current_cpu_data.fpu_csr31;         \
+                                                                       \
        p = personality(current->personality);                          \
        if (p != PER_LINUX32 && p != PER_LINUX)                         \
                set_personality(PER_LINUX);                             \
index dd083e999b08a14ffdbef46d5f5f4a0731e9f18e..83d50d563a0f5d5c556ac78cd16352af25958684 100644 (file)
@@ -30,7 +30,7 @@
 struct sigcontext;
 struct sigcontext32;
 
-extern void _init_fpu(void);
+extern void _init_fpu(unsigned int);
 extern void _save_fp(struct task_struct *);
 extern void _restore_fp(struct task_struct *);
 
@@ -182,6 +182,7 @@ static inline void lose_fpu(int save)
 
 static inline int init_fpu(void)
 {
+       unsigned int fcr31 = current->thread.fpu.fcr31;
        int ret = 0;
 
        if (cpu_has_fpu) {
@@ -192,7 +193,7 @@ static inline int init_fpu(void)
                        return ret;
 
                if (!cpu_has_fre) {
-                       _init_fpu();
+                       _init_fpu(fcr31);
 
                        return 0;
                }
@@ -206,7 +207,7 @@ static inline int init_fpu(void)
                config5 = clear_c0_config5(MIPS_CONF5_FRE);
                enable_fpu_hazard();
 
-               _init_fpu();
+               _init_fpu(fcr31);
 
                /* Restore FRE */
                write_c0_config5(config5);
index bfcc5c64b7b09745a822f78719b171841071d960..2f021cdfba4f8fc60a5348f7822f0014d02a3eff 100644 (file)
@@ -88,8 +88,6 @@ static inline void fpu_emulator_init_fpu(void)
        struct task_struct *t = current;
        int i;
 
-       t->thread.fpu.fcr31 = 0;
-
        for (i = 0; i < 32; i++)
                set_fpr64(&t->thread.fpu.fpr[i], 0, SIGNALLING_NAN);
 }
index ca9b9c62c6eacc675ad75d925f9d3390e1f38976..2911ad5977d79183859e88fbcb26acec7b89e775 100644 (file)
 #include <asm/spram.h>
 #include <asm/uaccess.h>
 
+/*
+ * Determine the FCSR mask for FPU hardware.
+ */
+static inline void cpu_set_fpu_fcsr_mask(struct cpuinfo_mips *c)
+{
+       unsigned long sr, mask, fcsr, fcsr0, fcsr1;
+
+       mask = FPU_CSR_ALL_X | FPU_CSR_ALL_E | FPU_CSR_ALL_S | FPU_CSR_RM;
+
+       sr = read_c0_status();
+       __enable_fpu(FPU_AS_IS);
+
+       fcsr = read_32bit_cp1_register(CP1_STATUS);
+
+       fcsr0 = fcsr & mask;
+       write_32bit_cp1_register(CP1_STATUS, fcsr0);
+       fcsr0 = read_32bit_cp1_register(CP1_STATUS);
+
+       fcsr1 = fcsr | ~mask;
+       write_32bit_cp1_register(CP1_STATUS, fcsr1);
+       fcsr1 = read_32bit_cp1_register(CP1_STATUS);
+
+       write_32bit_cp1_register(CP1_STATUS, fcsr);
+
+       write_c0_status(sr);
+
+       c->fpu_msk31 = ~(fcsr0 ^ fcsr1) & ~mask;
+}
+
 /*
  * Set the FIR feature flags for the FPU emulator.
  */
@@ -50,11 +79,15 @@ static void cpu_set_nofpu_id(struct cpuinfo_mips *c)
        c->fpu_id = value;
 }
 
+/* Determined FPU emulator mask to use for the boot CPU with "nofpu".  */
+static unsigned int mips_nofpu_msk31;
+
 static int mips_fpu_disabled;
 
 static int __init fpu_disable(char *s)
 {
        boot_cpu_data.options &= ~MIPS_CPU_FPU;
+       boot_cpu_data.fpu_msk31 = mips_nofpu_msk31;
        cpu_set_nofpu_id(&boot_cpu_data);
        mips_fpu_disabled = 1;
 
@@ -597,6 +630,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
        case PRID_IMP_R2000:
                c->cputype = CPU_R2000;
                __cpu_name[cpu] = "R2000";
+               c->fpu_msk31 |= FPU_CSR_CONDX | FPU_CSR_FS;
                c->options = MIPS_CPU_TLB | MIPS_CPU_3K_CACHE |
                             MIPS_CPU_NOFPUEX;
                if (__cpu_has_fpu())
@@ -616,6 +650,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                        c->cputype = CPU_R3000;
                        __cpu_name[cpu] = "R3000";
                }
+               c->fpu_msk31 |= FPU_CSR_CONDX | FPU_CSR_FS;
                c->options = MIPS_CPU_TLB | MIPS_CPU_3K_CACHE |
                             MIPS_CPU_NOFPUEX;
                if (__cpu_has_fpu())
@@ -664,6 +699,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                }
 
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_32FPR |
                             MIPS_CPU_WATCH | MIPS_CPU_VCE |
                             MIPS_CPU_LLSC;
@@ -671,6 +707,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                break;
        case PRID_IMP_VR41XX:
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS;
                c->tlbsize = 32;
                switch (c->processor_id & 0xf0) {
@@ -712,6 +749,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R4300;
                __cpu_name[cpu] = "R4300";
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_32FPR |
                             MIPS_CPU_LLSC;
                c->tlbsize = 32;
@@ -720,6 +758,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R4600;
                __cpu_name[cpu] = "R4600";
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_32FPR |
                             MIPS_CPU_LLSC;
                c->tlbsize = 48;
@@ -735,11 +774,13 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R4650;
                __cpu_name[cpu] = "R4650";
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_LLSC;
                c->tlbsize = 48;
                break;
        #endif
        case PRID_IMP_TX39:
+               c->fpu_msk31 |= FPU_CSR_CONDX | FPU_CSR_FS;
                c->options = MIPS_CPU_TLB | MIPS_CPU_TX39_CACHE;
 
                if ((c->processor_id & 0xf0) == (PRID_REV_TX3927 & 0xf0)) {
@@ -765,6 +806,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R4700;
                __cpu_name[cpu] = "R4700";
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_FPU | MIPS_CPU_32FPR |
                             MIPS_CPU_LLSC;
                c->tlbsize = 48;
@@ -773,6 +815,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_TX49XX;
                __cpu_name[cpu] = "R49XX";
                set_isa(c, MIPS_CPU_ISA_III);
+               c->fpu_msk31 |= FPU_CSR_CONDX;
                c->options = R4K_OPTS | MIPS_CPU_LLSC;
                if (!(c->processor_id & 0x08))
                        c->options |= MIPS_CPU_FPU | MIPS_CPU_32FPR;
@@ -814,6 +857,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R6000;
                __cpu_name[cpu] = "R6000";
                set_isa(c, MIPS_CPU_ISA_II);
+               c->fpu_msk31 |= FPU_CSR_CONDX | FPU_CSR_FS;
                c->options = MIPS_CPU_TLB | MIPS_CPU_FPU |
                             MIPS_CPU_LLSC;
                c->tlbsize = 32;
@@ -822,6 +866,7 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                c->cputype = CPU_R6000A;
                __cpu_name[cpu] = "R6000A";
                set_isa(c, MIPS_CPU_ISA_II);
+               c->fpu_msk31 |= FPU_CSR_CONDX | FPU_CSR_FS;
                c->options = MIPS_CPU_TLB | MIPS_CPU_FPU |
                             MIPS_CPU_LLSC;
                c->tlbsize = 32;
@@ -893,12 +938,14 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu)
                        __cpu_name[cpu] = "ICT Loongson-2";
                        set_elf_platform(cpu, "loongson2e");
                        set_isa(c, MIPS_CPU_ISA_III);
+                       c->fpu_msk31 |= FPU_CSR_CONDX;
                        break;
                case PRID_REV_LOONGSON2F:
                        c->cputype = CPU_LOONGSON2;
                        __cpu_name[cpu] = "ICT Loongson-2";
                        set_elf_platform(cpu, "loongson2f");
                        set_isa(c, MIPS_CPU_ISA_III);
+                       c->fpu_msk31 |= FPU_CSR_CONDX;
                        break;
                case PRID_REV_LOONGSON3A:
                        c->cputype = CPU_LOONGSON3;
@@ -1335,6 +1382,9 @@ void cpu_probe(void)
        c->cputype      = CPU_UNKNOWN;
        c->writecombine = _CACHE_UNCACHED;
 
+       c->fpu_csr31    = FPU_CSR_RN;
+       c->fpu_msk31    = FPU_CSR_RSVD | FPU_CSR_ABS2008 | FPU_CSR_NAN2008;
+
        c->processor_id = read_c0_prid();
        switch (c->processor_id & PRID_COMP_MASK) {
        case PRID_COMP_LEGACY:
@@ -1393,6 +1443,7 @@ void cpu_probe(void)
 
        if (c->options & MIPS_CPU_FPU) {
                c->fpu_id = cpu_get_fpu_id();
+               mips_nofpu_msk31 = c->fpu_msk31;
 
                if (c->isa_level & (MIPS_CPU_ISA_M32R1 | MIPS_CPU_ISA_M64R1 |
                                    MIPS_CPU_ISA_M32R2 | MIPS_CPU_ISA_M64R2 |
@@ -1402,6 +1453,8 @@ void cpu_probe(void)
                        if (c->fpu_id & MIPS_FPIR_FREP)
                                c->options |= MIPS_CPU_FRE;
                }
+
+               cpu_set_fpu_fcsr_mask(c);
        } else
                cpu_set_nofpu_id(c);
 
index 51045281259403c55fcefac09d510f874a3047bb..6d1e3f8005f79dc2506012249c7e87462322c5a1 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <asm/byteorder.h>
 #include <asm/cpu.h>
+#include <asm/cpu-info.h>
 #include <asm/dsp.h>
 #include <asm/fpu.h>
 #include <asm/mipsregs.h>
@@ -137,6 +138,9 @@ int ptrace_setfpregs(struct task_struct *child, __u32 __user *data)
 {
        union fpureg *fregs;
        u64 fpr_val;
+       u32 fcr31;
+       u32 value;
+       u32 mask;
        int i;
 
        if (!access_ok(VERIFY_READ, data, 33 * 8))
@@ -149,8 +153,10 @@ int ptrace_setfpregs(struct task_struct *child, __u32 __user *data)
                set_fpr64(&fregs[i], 0, fpr_val);
        }
 
-       __get_user(child->thread.fpu.fcr31, data + 64);
-       child->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X;
+       __get_user(value, data + 64);
+       fcr31 = child->thread.fpu.fcr31;
+       mask = current_cpu_data.fpu_msk31;
+       child->thread.fpu.fcr31 = (value & ~mask) | (fcr31 & mask);
 
        /* FIR may not be written.  */
 
index 435ea652f5fae7af10c392b884faecdc8ea430cf..5087a4b72e6b9a4ae12fc8acdb6f7f8f318784c5 100644 (file)
@@ -115,11 +115,9 @@ LEAF(_restore_fp)
  * the property that no matter whether considered as single or as double
  * precision represents signaling NANS.
  *
- * We initialize fcr31 to rounding to nearest, no exceptions.
+ * The value to initialize fcr31 to comes in $a0.
  */
 
-#define FPU_DEFAULT  0x00000000
-
        .set push
        SET_HARDFLOAT
 
@@ -129,8 +127,7 @@ LEAF(_init_fpu)
        or      t0, t1
        mtc0    t0, CP0_STATUS
 
-       li      t1, FPU_DEFAULT
-       ctc1    t1, fcr31
+       ctc1    a0, fcr31
 
        li      t0, -1
 
index 3b1a36f13a7dd915c235f11e073c805264f90960..04cbbde3521bc11cd8a1f6c678b2e6fa6bac0529 100644 (file)
@@ -165,11 +165,9 @@ LEAF(_init_msa_upper)
  * the property that no matter whether considered as single or as double
  * precision represents signaling NANS.
  *
- * We initialize fcr31 to rounding to nearest, no exceptions.
+ * The value to initialize fcr31 to comes in $a0.
  */
 
-#define FPU_DEFAULT  0x00000000
-
        .set push
        SET_HARDFLOAT
 
@@ -180,8 +178,7 @@ LEAF(_init_fpu)
        mtc0    t0, CP0_STATUS
        enable_fpu_hazard
 
-       li      t1, FPU_DEFAULT
-       ctc1    t1, fcr31
+       ctc1    a0, fcr31
 
        li      t1, -1                          # SNaN
 
index b03e37d2071ac867e65a702f7e052a00c13f496c..ea13764d0a035ccc5ec6cf443f021aa1f4f58103 100644 (file)
@@ -43,7 +43,7 @@ static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action,
                if (!fpu_owned) {
                        set_thread_flag(TIF_USEDFPU);
                        if (!used_math()) {
-                               _init_fpu();
+                               _init_fpu(current->thread.fpu.fcr31);
                                set_used_math();
                        } else
                                _restore_fp(current);
index 3a90170a627737be3c0f7c3dc5615c8cf6bb5190..d31c537ace1d11b7f742fd9ead71d3f273cfa2d3 100644 (file)
@@ -908,6 +908,7 @@ static inline void cop1_ctc(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
 {
        u32 fcr31 = ctx->fcr31;
        u32 value;
+       u32 mask;
 
        if (MIPSInst_RT(ir) == 0)
                value = 0;
@@ -919,9 +920,9 @@ static inline void cop1_ctc(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
                pr_debug("%p gpr[%d]->csr=%08x\n",
                         (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
 
-               /* Don't write unsupported bits.  */
-               fcr31 = value &
-                       ~(FPU_CSR_RSVD | FPU_CSR_ABS2008 | FPU_CSR_NAN2008);
+               /* Preserve read-only bits.  */
+               mask = current_cpu_data.fpu_msk31;
+               fcr31 = (value & ~mask) | (fcr31 & mask);
                break;
 
        case FPCREG_FENR: