riscv: Correct the initialized flow of FP register
authorVincent Chen <vincent.chen@sifive.com>
Wed, 14 Aug 2019 08:23:52 +0000 (16:23 +0800)
committerPaul Walmsley <paul.walmsley@sifive.com>
Wed, 14 Aug 2019 20:11:11 +0000 (13:11 -0700)
  The following two reasons cause FP registers are sometimes not
initialized before starting the user program.
1. Currently, the FP context is initialized in flush_thread() function
   and we expect these initial values to be restored to FP register when
   doing FP context switch. However, the FP context switch only occurs in
   switch_to function. Hence, if this process does not be scheduled out
   and scheduled in before entering the user space, the FP registers
   have no chance to initialize.
2. In flush_thread(), the state of reg->sstatus.FS inherits from the
   parent. Hence, the state of reg->sstatus.FS may be dirty. If this
   process is scheduled out during flush_thread() and initializing the
   FP register, the fstate_save() in switch_to will corrupt the FP context
   which has been initialized until flush_thread().

  To solve the 1st case, the initialization of the FP register will be
completed in start_thread(). It makes sure all FP registers are initialized
before starting the user program. For the 2nd case, the state of
reg->sstatus.FS in start_thread will be set to SR_FS_OFF to prevent this
process from corrupting FP context in doing context save. The FP state is
set to SR_FS_INITIAL in start_trhead().

Signed-off-by: Vincent Chen <vincent.chen@sifive.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Fixes: 7db91e57a0acd ("RISC-V: Task implementation")
Cc: stable@vger.kernel.org
[paul.walmsley@sifive.com: fixed brace alignment issue reported by
 checkpatch]
Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
arch/riscv/include/asm/switch_to.h
arch/riscv/kernel/process.c

index 853b65ef656da42d52b5c3c46f9ac8f75ce8774d..949d9cd91dec4980d69adfd563e5a303de932d6a 100644 (file)
@@ -19,6 +19,12 @@ static inline void __fstate_clean(struct pt_regs *regs)
        regs->sstatus |= (regs->sstatus & ~(SR_FS)) | SR_FS_CLEAN;
 }
 
+static inline void fstate_off(struct task_struct *task,
+                             struct pt_regs *regs)
+{
+       regs->sstatus = (regs->sstatus & ~SR_FS) | SR_FS_OFF;
+}
+
 static inline void fstate_save(struct task_struct *task,
                               struct pt_regs *regs)
 {
index f23794bd1e90c2f1dad70d1d9ed18507c2b58cd1..fb3a082362eb87554ff297f3e026d9663c5a8044 100644 (file)
@@ -64,8 +64,14 @@ void start_thread(struct pt_regs *regs, unsigned long pc,
        unsigned long sp)
 {
        regs->sstatus = SR_SPIE;
-       if (has_fpu)
+       if (has_fpu) {
                regs->sstatus |= SR_FS_INITIAL;
+               /*
+                * Restore the initial value to the FP register
+                * before starting the user program.
+                */
+               fstate_restore(current, regs);
+       }
        regs->sepc = pc;
        regs->sp = sp;
        set_fs(USER_DS);
@@ -75,10 +81,11 @@ void flush_thread(void)
 {
 #ifdef CONFIG_FPU
        /*
-        * Reset FPU context
+        * Reset FPU state and context
         *      frm: round to nearest, ties to even (IEEE default)
         *      fflags: accrued exceptions cleared
         */
+       fstate_off(current, task_pt_regs(current));
        memset(&current->thread.fstate, 0, sizeof(current->thread.fstate));
 #endif
 }