mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-25 17:45:11 +07:00
64bedffe49
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: James Hogan <james.hogan@imgtec.com> Acked-by: Ralf 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
544 lines
11 KiB
ArmAsm
544 lines
11 KiB
ArmAsm
/*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*
|
|
* Copyright (C) 1994 - 2000, 2001, 2003 Ralf Baechle
|
|
* Copyright (C) 1999, 2000 Silicon Graphics, Inc.
|
|
* Copyright (C) 2002, 2007 Maciej W. Rozycki
|
|
* Copyright (C) 2001, 2012 MIPS Technologies, Inc. All rights reserved.
|
|
*/
|
|
#include <linux/init.h>
|
|
|
|
#include <asm/asm.h>
|
|
#include <asm/asmmacro.h>
|
|
#include <asm/cacheops.h>
|
|
#include <asm/irqflags.h>
|
|
#include <asm/regdef.h>
|
|
#include <asm/fpregdef.h>
|
|
#include <asm/mipsregs.h>
|
|
#include <asm/stackframe.h>
|
|
#include <asm/war.h>
|
|
#include <asm/thread_info.h>
|
|
|
|
__INIT
|
|
|
|
/*
|
|
* General exception vector for all other CPUs.
|
|
*
|
|
* Be careful when changing this, it has to be at most 128 bytes
|
|
* to fit into space reserved for the exception handler.
|
|
*/
|
|
NESTED(except_vec3_generic, 0, sp)
|
|
.set push
|
|
.set noat
|
|
#if R5432_CP0_INTERRUPT_WAR
|
|
mfc0 k0, CP0_INDEX
|
|
#endif
|
|
mfc0 k1, CP0_CAUSE
|
|
andi k1, k1, 0x7c
|
|
#ifdef CONFIG_64BIT
|
|
dsll k1, k1, 1
|
|
#endif
|
|
PTR_L k0, exception_handlers(k1)
|
|
jr k0
|
|
.set pop
|
|
END(except_vec3_generic)
|
|
|
|
/*
|
|
* General exception handler for CPUs with virtual coherency exception.
|
|
*
|
|
* Be careful when changing this, it has to be at most 256 (as a special
|
|
* exception) bytes to fit into space reserved for the exception handler.
|
|
*/
|
|
NESTED(except_vec3_r4000, 0, sp)
|
|
.set push
|
|
.set arch=r4000
|
|
.set noat
|
|
mfc0 k1, CP0_CAUSE
|
|
li k0, 31<<2
|
|
andi k1, k1, 0x7c
|
|
.set push
|
|
.set noreorder
|
|
.set nomacro
|
|
beq k1, k0, handle_vced
|
|
li k0, 14<<2
|
|
beq k1, k0, handle_vcei
|
|
#ifdef CONFIG_64BIT
|
|
dsll k1, k1, 1
|
|
#endif
|
|
.set pop
|
|
PTR_L k0, exception_handlers(k1)
|
|
jr k0
|
|
|
|
/*
|
|
* Big shit, we now may have two dirty primary cache lines for the same
|
|
* physical address. We can safely invalidate the line pointed to by
|
|
* c0_badvaddr because after return from this exception handler the
|
|
* load / store will be re-executed.
|
|
*/
|
|
handle_vced:
|
|
MFC0 k0, CP0_BADVADDR
|
|
li k1, -4 # Is this ...
|
|
and k0, k1 # ... really needed?
|
|
mtc0 zero, CP0_TAGLO
|
|
cache Index_Store_Tag_D, (k0)
|
|
cache Hit_Writeback_Inv_SD, (k0)
|
|
#ifdef CONFIG_PROC_FS
|
|
PTR_LA k0, vced_count
|
|
lw k1, (k0)
|
|
addiu k1, 1
|
|
sw k1, (k0)
|
|
#endif
|
|
eret
|
|
|
|
handle_vcei:
|
|
MFC0 k0, CP0_BADVADDR
|
|
cache Hit_Writeback_Inv_SD, (k0) # also cleans pi
|
|
#ifdef CONFIG_PROC_FS
|
|
PTR_LA k0, vcei_count
|
|
lw k1, (k0)
|
|
addiu k1, 1
|
|
sw k1, (k0)
|
|
#endif
|
|
eret
|
|
.set pop
|
|
END(except_vec3_r4000)
|
|
|
|
__FINIT
|
|
|
|
.align 5 /* 32 byte rollback region */
|
|
LEAF(__r4k_wait)
|
|
.set push
|
|
.set noreorder
|
|
/* start of rollback region */
|
|
LONG_L t0, TI_FLAGS($28)
|
|
nop
|
|
andi t0, _TIF_NEED_RESCHED
|
|
bnez t0, 1f
|
|
nop
|
|
nop
|
|
nop
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
#endif
|
|
.set MIPS_ISA_ARCH_LEVEL_RAW
|
|
wait
|
|
/* end of rollback region (the region size must be power of two) */
|
|
1:
|
|
jr ra
|
|
nop
|
|
.set pop
|
|
END(__r4k_wait)
|
|
|
|
.macro BUILD_ROLLBACK_PROLOGUE handler
|
|
FEXPORT(rollback_\handler)
|
|
.set push
|
|
.set noat
|
|
MFC0 k0, CP0_EPC
|
|
PTR_LA k1, __r4k_wait
|
|
ori k0, 0x1f /* 32 byte rollback region */
|
|
xori k0, 0x1f
|
|
bne k0, k1, 9f
|
|
MTC0 k0, CP0_EPC
|
|
9:
|
|
.set pop
|
|
.endm
|
|
|
|
.align 5
|
|
BUILD_ROLLBACK_PROLOGUE handle_int
|
|
NESTED(handle_int, PT_SIZE, sp)
|
|
#ifdef CONFIG_TRACE_IRQFLAGS
|
|
/*
|
|
* Check to see if the interrupted code has just disabled
|
|
* interrupts and ignore this interrupt for now if so.
|
|
*
|
|
* local_irq_disable() disables interrupts and then calls
|
|
* trace_hardirqs_off() to track the state. If an interrupt is taken
|
|
* after interrupts are disabled but before the state is updated
|
|
* it will appear to restore_all that it is incorrectly returning with
|
|
* interrupts disabled
|
|
*/
|
|
.set push
|
|
.set noat
|
|
mfc0 k0, CP0_STATUS
|
|
#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
|
|
and k0, ST0_IEP
|
|
bnez k0, 1f
|
|
|
|
mfc0 k0, CP0_EPC
|
|
.set noreorder
|
|
j k0
|
|
rfe
|
|
#else
|
|
and k0, ST0_IE
|
|
bnez k0, 1f
|
|
|
|
eret
|
|
#endif
|
|
1:
|
|
.set pop
|
|
#endif
|
|
SAVE_ALL
|
|
CLI
|
|
TRACE_IRQS_OFF
|
|
|
|
LONG_L s0, TI_REGS($28)
|
|
LONG_S sp, TI_REGS($28)
|
|
PTR_LA ra, ret_from_irq
|
|
PTR_LA v0, plat_irq_dispatch
|
|
jr v0
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
nop
|
|
#endif
|
|
END(handle_int)
|
|
|
|
__INIT
|
|
|
|
/*
|
|
* Special interrupt vector for MIPS64 ISA & embedded MIPS processors.
|
|
* This is a dedicated interrupt exception vector which reduces the
|
|
* interrupt processing overhead. The jump instruction will be replaced
|
|
* at the initialization time.
|
|
*
|
|
* Be careful when changing this, it has to be at most 128 bytes
|
|
* to fit into space reserved for the exception handler.
|
|
*/
|
|
NESTED(except_vec4, 0, sp)
|
|
1: j 1b /* Dummy, will be replaced */
|
|
END(except_vec4)
|
|
|
|
/*
|
|
* EJTAG debug exception handler.
|
|
* The EJTAG debug exception entry point is 0xbfc00480, which
|
|
* normally is in the boot PROM, so the boot PROM must do an
|
|
* unconditional jump to this vector.
|
|
*/
|
|
NESTED(except_vec_ejtag_debug, 0, sp)
|
|
j ejtag_debug_handler
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
nop
|
|
#endif
|
|
END(except_vec_ejtag_debug)
|
|
|
|
__FINIT
|
|
|
|
/*
|
|
* Vectored interrupt handler.
|
|
* This prototype is copied to ebase + n*IntCtl.VS and patched
|
|
* to invoke the handler
|
|
*/
|
|
BUILD_ROLLBACK_PROLOGUE except_vec_vi
|
|
NESTED(except_vec_vi, 0, sp)
|
|
SAVE_SOME
|
|
SAVE_AT
|
|
.set push
|
|
.set noreorder
|
|
PTR_LA v1, except_vec_vi_handler
|
|
FEXPORT(except_vec_vi_lui)
|
|
lui v0, 0 /* Patched */
|
|
jr v1
|
|
FEXPORT(except_vec_vi_ori)
|
|
ori v0, 0 /* Patched */
|
|
.set pop
|
|
END(except_vec_vi)
|
|
EXPORT(except_vec_vi_end)
|
|
|
|
/*
|
|
* Common Vectored Interrupt code
|
|
* Complete the register saves and invoke the handler which is passed in $v0
|
|
*/
|
|
NESTED(except_vec_vi_handler, 0, sp)
|
|
SAVE_TEMP
|
|
SAVE_STATIC
|
|
CLI
|
|
#ifdef CONFIG_TRACE_IRQFLAGS
|
|
move s0, v0
|
|
TRACE_IRQS_OFF
|
|
move v0, s0
|
|
#endif
|
|
|
|
LONG_L s0, TI_REGS($28)
|
|
LONG_S sp, TI_REGS($28)
|
|
PTR_LA ra, ret_from_irq
|
|
jr v0
|
|
END(except_vec_vi_handler)
|
|
|
|
/*
|
|
* EJTAG debug exception handler.
|
|
*/
|
|
NESTED(ejtag_debug_handler, PT_SIZE, sp)
|
|
.set push
|
|
.set noat
|
|
MTC0 k0, CP0_DESAVE
|
|
mfc0 k0, CP0_DEBUG
|
|
|
|
sll k0, k0, 30 # Check for SDBBP.
|
|
bgez k0, ejtag_return
|
|
|
|
PTR_LA k0, ejtag_debug_buffer
|
|
LONG_S k1, 0(k0)
|
|
SAVE_ALL
|
|
move a0, sp
|
|
jal ejtag_exception_handler
|
|
RESTORE_ALL
|
|
PTR_LA k0, ejtag_debug_buffer
|
|
LONG_L k1, 0(k0)
|
|
|
|
ejtag_return:
|
|
MFC0 k0, CP0_DESAVE
|
|
.set mips32
|
|
deret
|
|
.set pop
|
|
END(ejtag_debug_handler)
|
|
|
|
/*
|
|
* This buffer is reserved for the use of the EJTAG debug
|
|
* handler.
|
|
*/
|
|
.data
|
|
EXPORT(ejtag_debug_buffer)
|
|
.fill LONGSIZE
|
|
.previous
|
|
|
|
__INIT
|
|
|
|
/*
|
|
* NMI debug exception handler for MIPS reference boards.
|
|
* The NMI debug exception entry point is 0xbfc00000, which
|
|
* normally is in the boot PROM, so the boot PROM must do a
|
|
* unconditional jump to this vector.
|
|
*/
|
|
NESTED(except_vec_nmi, 0, sp)
|
|
j nmi_handler
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
nop
|
|
#endif
|
|
END(except_vec_nmi)
|
|
|
|
__FINIT
|
|
|
|
NESTED(nmi_handler, PT_SIZE, sp)
|
|
.set push
|
|
.set noat
|
|
/*
|
|
* Clear ERL - restore segment mapping
|
|
* Clear BEV - required for page fault exception handler to work
|
|
*/
|
|
mfc0 k0, CP0_STATUS
|
|
ori k0, k0, ST0_EXL
|
|
li k1, ~(ST0_BEV | ST0_ERL)
|
|
and k0, k0, k1
|
|
mtc0 k0, CP0_STATUS
|
|
_ehb
|
|
SAVE_ALL
|
|
move a0, sp
|
|
jal nmi_exception_handler
|
|
/* nmi_exception_handler never returns */
|
|
.set pop
|
|
END(nmi_handler)
|
|
|
|
.macro __build_clear_none
|
|
.endm
|
|
|
|
.macro __build_clear_sti
|
|
TRACE_IRQS_ON
|
|
STI
|
|
.endm
|
|
|
|
.macro __build_clear_cli
|
|
CLI
|
|
TRACE_IRQS_OFF
|
|
.endm
|
|
|
|
.macro __build_clear_fpe
|
|
.set push
|
|
/* gas fails to assemble cfc1 for some archs (octeon).*/ \
|
|
.set mips1
|
|
SET_HARDFLOAT
|
|
cfc1 a1, fcr31
|
|
.set pop
|
|
CLI
|
|
TRACE_IRQS_OFF
|
|
.endm
|
|
|
|
.macro __build_clear_msa_fpe
|
|
_cfcmsa a1, MSA_CSR
|
|
CLI
|
|
TRACE_IRQS_OFF
|
|
.endm
|
|
|
|
.macro __build_clear_ade
|
|
MFC0 t0, CP0_BADVADDR
|
|
PTR_S t0, PT_BVADDR(sp)
|
|
KMODE
|
|
.endm
|
|
|
|
.macro __BUILD_silent exception
|
|
.endm
|
|
|
|
/* Gas tries to parse the PRINT argument as a string containing
|
|
string escapes and emits bogus warnings if it believes to
|
|
recognize an unknown escape code. So make the arguments
|
|
start with an n and gas will believe \n is ok ... */
|
|
.macro __BUILD_verbose nexception
|
|
LONG_L a1, PT_EPC(sp)
|
|
#ifdef CONFIG_32BIT
|
|
PRINT("Got \nexception at %08lx\012")
|
|
#endif
|
|
#ifdef CONFIG_64BIT
|
|
PRINT("Got \nexception at %016lx\012")
|
|
#endif
|
|
.endm
|
|
|
|
.macro __BUILD_count exception
|
|
LONG_L t0,exception_count_\exception
|
|
LONG_ADDIU t0, 1
|
|
LONG_S t0,exception_count_\exception
|
|
.comm exception_count\exception, 8, 8
|
|
.endm
|
|
|
|
.macro __BUILD_HANDLER exception handler clear verbose ext
|
|
.align 5
|
|
NESTED(handle_\exception, PT_SIZE, sp)
|
|
.set noat
|
|
SAVE_ALL
|
|
FEXPORT(handle_\exception\ext)
|
|
__BUILD_clear_\clear
|
|
.set at
|
|
__BUILD_\verbose \exception
|
|
move a0, sp
|
|
PTR_LA ra, ret_from_exception
|
|
j do_\handler
|
|
END(handle_\exception)
|
|
.endm
|
|
|
|
.macro BUILD_HANDLER exception handler clear verbose
|
|
__BUILD_HANDLER \exception \handler \clear \verbose _int
|
|
.endm
|
|
|
|
BUILD_HANDLER adel ade ade silent /* #4 */
|
|
BUILD_HANDLER ades ade ade silent /* #5 */
|
|
BUILD_HANDLER ibe be cli silent /* #6 */
|
|
BUILD_HANDLER dbe be cli silent /* #7 */
|
|
BUILD_HANDLER bp bp sti silent /* #9 */
|
|
BUILD_HANDLER ri ri sti silent /* #10 */
|
|
BUILD_HANDLER cpu cpu sti silent /* #11 */
|
|
BUILD_HANDLER ov ov sti silent /* #12 */
|
|
BUILD_HANDLER tr tr sti silent /* #13 */
|
|
BUILD_HANDLER msa_fpe msa_fpe msa_fpe silent /* #14 */
|
|
BUILD_HANDLER fpe fpe fpe silent /* #15 */
|
|
BUILD_HANDLER ftlb ftlb none silent /* #16 */
|
|
BUILD_HANDLER msa msa sti silent /* #21 */
|
|
BUILD_HANDLER mdmx mdmx sti silent /* #22 */
|
|
#ifdef CONFIG_HARDWARE_WATCHPOINTS
|
|
/*
|
|
* For watch, interrupts will be enabled after the watch
|
|
* registers are read.
|
|
*/
|
|
BUILD_HANDLER watch watch cli silent /* #23 */
|
|
#else
|
|
BUILD_HANDLER watch watch sti verbose /* #23 */
|
|
#endif
|
|
BUILD_HANDLER mcheck mcheck cli verbose /* #24 */
|
|
BUILD_HANDLER mt mt sti silent /* #25 */
|
|
BUILD_HANDLER dsp dsp sti silent /* #26 */
|
|
BUILD_HANDLER reserved reserved sti verbose /* others */
|
|
|
|
.align 5
|
|
LEAF(handle_ri_rdhwr_vivt)
|
|
.set push
|
|
.set noat
|
|
.set noreorder
|
|
/* check if TLB contains a entry for EPC */
|
|
MFC0 k1, CP0_ENTRYHI
|
|
andi k1, 0xff /* ASID_MASK */
|
|
MFC0 k0, CP0_EPC
|
|
PTR_SRL k0, _PAGE_SHIFT + 1
|
|
PTR_SLL k0, _PAGE_SHIFT + 1
|
|
or k1, k0
|
|
MTC0 k1, CP0_ENTRYHI
|
|
mtc0_tlbw_hazard
|
|
tlbp
|
|
tlb_probe_hazard
|
|
mfc0 k1, CP0_INDEX
|
|
.set pop
|
|
bltz k1, handle_ri /* slow path */
|
|
/* fall thru */
|
|
END(handle_ri_rdhwr_vivt)
|
|
|
|
LEAF(handle_ri_rdhwr)
|
|
.set push
|
|
.set noat
|
|
.set noreorder
|
|
/* MIPS32: 0x7c03e83b: rdhwr v1,$29 */
|
|
/* microMIPS: 0x007d6b3c: rdhwr v1,$29 */
|
|
MFC0 k1, CP0_EPC
|
|
#if defined(CONFIG_CPU_MICROMIPS) || defined(CONFIG_CPU_MIPS32_R2) || defined(CONFIG_CPU_MIPS64_R2)
|
|
and k0, k1, 1
|
|
beqz k0, 1f
|
|
xor k1, k0
|
|
lhu k0, (k1)
|
|
lhu k1, 2(k1)
|
|
ins k1, k0, 16, 16
|
|
lui k0, 0x007d
|
|
b docheck
|
|
ori k0, 0x6b3c
|
|
1:
|
|
lui k0, 0x7c03
|
|
lw k1, (k1)
|
|
ori k0, 0xe83b
|
|
#else
|
|
andi k0, k1, 1
|
|
bnez k0, handle_ri
|
|
lui k0, 0x7c03
|
|
lw k1, (k1)
|
|
ori k0, 0xe83b
|
|
#endif
|
|
.set reorder
|
|
docheck:
|
|
bne k0, k1, handle_ri /* if not ours */
|
|
|
|
isrdhwr:
|
|
/* The insn is rdhwr. No need to check CAUSE.BD here. */
|
|
get_saved_sp /* k1 := current_thread_info */
|
|
.set noreorder
|
|
MFC0 k0, CP0_EPC
|
|
#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
|
|
ori k1, _THREAD_MASK
|
|
xori k1, _THREAD_MASK
|
|
LONG_L v1, TI_TP_VALUE(k1)
|
|
LONG_ADDIU k0, 4
|
|
jr k0
|
|
rfe
|
|
#else
|
|
#ifndef CONFIG_CPU_DADDI_WORKAROUNDS
|
|
LONG_ADDIU k0, 4 /* stall on $k0 */
|
|
#else
|
|
.set at=v1
|
|
LONG_ADDIU k0, 4
|
|
.set noat
|
|
#endif
|
|
MTC0 k0, CP0_EPC
|
|
/* I hope three instructions between MTC0 and ERET are enough... */
|
|
ori k1, _THREAD_MASK
|
|
xori k1, _THREAD_MASK
|
|
LONG_L v1, TI_TP_VALUE(k1)
|
|
.set arch=r4000
|
|
eret
|
|
.set mips0
|
|
#endif
|
|
.set pop
|
|
END(handle_ri_rdhwr)
|
|
|
|
#ifdef CONFIG_64BIT
|
|
/* A temporary overflow handler used by check_daddi(). */
|
|
|
|
__INIT
|
|
|
|
BUILD_HANDLER daddi_ov daddi_ov none silent /* #12 */
|
|
#endif
|