Commit 64bedffe authored by James Hogan's avatar James Hogan

MIPS: Clear [MSA]FPE CSR.Cause after notify_die()

When handling floating point exceptions (FPEs) and MSA FPEs the Cause
bits of the appropriate control and status register (FCSR for FPEs and
MSACSR for MSA FPEs) are read and cleared before enabling interrupts,
presumably so that it doesn't have to go through the pain of restoring
those bits if the process is pre-empted, since writing those bits would
cause another immediate exception while still in the kernel.

The bits aren't normally ever restored again, since userland never
expects to see them set.

However for virtualisation it is necessary for the kernel to be able to
restore these Cause bits, as the guest may have been interrupted in an
FP exception handler but before it could read the Cause bits. This can
be done by registering a die notifier, to get notified of the exception
when such a value is restored, and if the PC was at the instruction
which is used to restore the guest state, the handler can step over it
and continue execution. The Cause bits can then remain set without
causing further exceptions.

For this to work safely a few changes are made:
- __build_clear_fpe and __build_clear_msa_fpe no longer clear the Cause
  bits, and now return from exception level with interrupts disabled
  instead of enabled.
- do_fpe() now clears the Cause bits and enables interrupts after
  notify_die() is called, so that the notifier can chose to return from
  exception without this happening.
- do_msa_fpe() acts similarly, but now actually makes use of the second
  argument (msacsr) and calls notify_die() with the new DIE_MSAFP,
  allowing die notifiers to be informed of MSA FPEs too.
Signed-off-by: default avatarJames Hogan <james.hogan@imgtec.com>
Acked-by: default avatarRalf Baechle <ralf@linux-mips.org>
Cc: Paul Burton <paul.burton@imgtec.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Gleb Natapov <gleb@kernel.org>
Cc: linux-mips@linux-mips.org
Cc: kvm@vger.kernel.org
parent 98119ad5
...@@ -10,7 +10,8 @@ enum die_val { ...@@ -10,7 +10,8 @@ enum die_val {
DIE_RI, DIE_RI,
DIE_PAGE_FAULT, DIE_PAGE_FAULT,
DIE_BREAK, DIE_BREAK,
DIE_SSTEPBP DIE_SSTEPBP,
DIE_MSAFP
}; };
#endif /* _ASM_MIPS_KDEBUG_H */ #endif /* _ASM_MIPS_KDEBUG_H */
...@@ -360,21 +360,15 @@ NESTED(nmi_handler, PT_SIZE, sp) ...@@ -360,21 +360,15 @@ NESTED(nmi_handler, PT_SIZE, sp)
.set mips1 .set mips1
SET_HARDFLOAT SET_HARDFLOAT
cfc1 a1, fcr31 cfc1 a1, fcr31
li a2, ~(0x3f << 12)
and a2, a1
ctc1 a2, fcr31
.set pop .set pop
TRACE_IRQS_ON CLI
STI TRACE_IRQS_OFF
.endm .endm
.macro __build_clear_msa_fpe .macro __build_clear_msa_fpe
_cfcmsa a1, MSA_CSR _cfcmsa a1, MSA_CSR
li a2, ~(0x3f << 12) CLI
and a1, a1, a2 TRACE_IRQS_OFF
_ctcmsa MSA_CSR, a1
TRACE_IRQS_ON
STI
.endm .endm
.macro __build_clear_ade .macro __build_clear_ade
......
...@@ -788,6 +788,11 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31) ...@@ -788,6 +788,11 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
if (notify_die(DIE_FP, "FP exception", regs, 0, regs_to_trapnr(regs), if (notify_die(DIE_FP, "FP exception", regs, 0, regs_to_trapnr(regs),
SIGFPE) == NOTIFY_STOP) SIGFPE) == NOTIFY_STOP)
goto out; goto out;
/* Clear FCSR.Cause before enabling interrupts */
write_32bit_cp1_register(CP1_STATUS, fcr31 & ~FPU_CSR_ALL_X);
local_irq_enable();
die_if_kernel("FP exception in kernel code", regs); die_if_kernel("FP exception in kernel code", regs);
if (fcr31 & FPU_CSR_UNI_X) { if (fcr31 & FPU_CSR_UNI_X) {
...@@ -1393,13 +1398,22 @@ asmlinkage void do_cpu(struct pt_regs *regs) ...@@ -1393,13 +1398,22 @@ asmlinkage void do_cpu(struct pt_regs *regs)
exception_exit(prev_state); exception_exit(prev_state);
} }
asmlinkage void do_msa_fpe(struct pt_regs *regs) asmlinkage void do_msa_fpe(struct pt_regs *regs, unsigned int msacsr)
{ {
enum ctx_state prev_state; enum ctx_state prev_state;
prev_state = exception_enter(); prev_state = exception_enter();
if (notify_die(DIE_MSAFP, "MSA FP exception", regs, 0,
regs_to_trapnr(regs), SIGFPE) == NOTIFY_STOP)
goto out;
/* Clear MSACSR.Cause before enabling interrupts */
write_msa_csr(msacsr & ~MSA_CSR_CAUSEF);
local_irq_enable();
die_if_kernel("do_msa_fpe invoked from kernel context!", regs); die_if_kernel("do_msa_fpe invoked from kernel context!", regs);
force_sig(SIGFPE, current); force_sig(SIGFPE, current);
out:
exception_exit(prev_state); exception_exit(prev_state);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment