| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * linux/arch/arm/vfp/vfphw.S |
| * |
| * Copyright (C) 2004 ARM Limited. |
| * Written by Deep Blue Solutions Limited. |
| * |
| * This code is called from the kernel's undefined instruction trap. |
| * r1 holds the thread_info pointer |
| * r3 holds the return address for successful handling. |
| * lr holds the return address for unrecognised instructions. |
| * sp points to a struct pt_regs (as defined in include/asm/proc/ptrace.h) |
| */ |
| #include <linux/init.h> |
| #include <linux/linkage.h> |
| #include <asm/thread_info.h> |
| #include <asm/vfpmacros.h> |
| #include <linux/kern_levels.h> |
| #include <asm/assembler.h> |
| #include <asm/asm-offsets.h> |
| |
| .macro DBGSTR, str |
| #ifdef DEBUG |
| stmfd sp!, {r0-r3, ip, lr} |
| ldr r0, =1f |
| bl _printk |
| ldmfd sp!, {r0-r3, ip, lr} |
| |
| .pushsection .rodata, "a" |
| 1: .ascii KERN_DEBUG "VFP: \str\n" |
| .byte 0 |
| .previous |
| #endif |
| .endm |
| |
| .macro DBGSTR1, str, arg |
| #ifdef DEBUG |
| stmfd sp!, {r0-r3, ip, lr} |
| mov r1, \arg |
| ldr r0, =1f |
| bl _printk |
| ldmfd sp!, {r0-r3, ip, lr} |
| |
| .pushsection .rodata, "a" |
| 1: .ascii KERN_DEBUG "VFP: \str\n" |
| .byte 0 |
| .previous |
| #endif |
| .endm |
| |
| .macro DBGSTR3, str, arg1, arg2, arg3 |
| #ifdef DEBUG |
| stmfd sp!, {r0-r3, ip, lr} |
| mov r3, \arg3 |
| mov r2, \arg2 |
| mov r1, \arg1 |
| ldr r0, =1f |
| bl _printk |
| ldmfd sp!, {r0-r3, ip, lr} |
| |
| .pushsection .rodata, "a" |
| 1: .ascii KERN_DEBUG "VFP: \str\n" |
| .byte 0 |
| .previous |
| #endif |
| .endm |
| |
| |
| @ VFP hardware support entry point. |
| @ |
| @ r0 = instruction opcode (32-bit ARM or two 16-bit Thumb) |
| @ r1 = thread_info pointer |
| @ r2 = PC value to resume execution after successful emulation |
| @ r3 = normal "successful" return address |
| @ lr = unrecognised instruction return address |
| @ IRQs enabled. |
| ENTRY(vfp_support_entry) |
| ldr r11, [r1, #TI_CPU] @ CPU number |
| add r10, r1, #TI_VFPSTATE @ r10 = workspace |
| |
| DBGSTR3 "instr %08x pc %08x state %p", r0, r2, r10 |
| |
| .fpu vfpv2 |
| VFPFMRX r1, FPEXC @ Is the VFP enabled? |
| DBGSTR1 "fpexc %08x", r1 |
| tst r1, #FPEXC_EN |
| bne look_for_VFP_exceptions @ VFP is already enabled |
| |
| DBGSTR1 "enable %x", r10 |
| ldr r9, vfp_current_hw_state_address |
| orr r1, r1, #FPEXC_EN @ user FPEXC has the enable bit set |
| ldr r4, [r9, r11, lsl #2] @ vfp_current_hw_state pointer |
| bic r5, r1, #FPEXC_EX @ make sure exceptions are disabled |
| cmp r4, r10 @ this thread owns the hw context? |
| #ifndef CONFIG_SMP |
| @ For UP, checking that this thread owns the hw context is |
| @ sufficient to determine that the hardware state is valid. |
| beq vfp_hw_state_valid |
| |
| @ On UP, we lazily save the VFP context. As a different |
| @ thread wants ownership of the VFP hardware, save the old |
| @ state if there was a previous (valid) owner. |
| |
| VFPFMXR FPEXC, r5 @ enable VFP, disable any pending |
| @ exceptions, so we can get at the |
| @ rest of it |
| |
| DBGSTR1 "save old state %p", r4 |
| cmp r4, #0 @ if the vfp_current_hw_state is NULL |
| beq vfp_reload_hw @ then the hw state needs reloading |
| VFPFSTMIA r4, r5 @ save the working registers |
| VFPFMRX r5, FPSCR @ current status |
| #ifndef CONFIG_CPU_FEROCEON |
| tst r1, #FPEXC_EX @ is there additional state to save? |
| beq 1f |
| VFPFMRX r6, FPINST @ FPINST (only if FPEXC.EX is set) |
| tst r1, #FPEXC_FP2V @ is there an FPINST2 to read? |
| beq 1f |
| VFPFMRX r8, FPINST2 @ FPINST2 if needed (and present) |
| 1: |
| #endif |
| stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2 |
| vfp_reload_hw: |
| |
| #else |
| @ For SMP, if this thread does not own the hw context, then we |
| @ need to reload it. No need to save the old state as on SMP, |
| @ we always save the state when we switch away from a thread. |
| bne vfp_reload_hw |
| |
| @ This thread has ownership of the current hardware context. |
| @ However, it may have been migrated to another CPU, in which |
| @ case the saved state is newer than the hardware context. |
| @ Check this by looking at the CPU number which the state was |
| @ last loaded onto. |
| ldr ip, [r10, #VFP_CPU] |
| teq ip, r11 |
| beq vfp_hw_state_valid |
| |
| vfp_reload_hw: |
| @ We're loading this threads state into the VFP hardware. Update |
| @ the CPU number which contains the most up to date VFP context. |
| str r11, [r10, #VFP_CPU] |
| |
| VFPFMXR FPEXC, r5 @ enable VFP, disable any pending |
| @ exceptions, so we can get at the |
| @ rest of it |
| #endif |
| |
| DBGSTR1 "load state %p", r10 |
| str r10, [r9, r11, lsl #2] @ update the vfp_current_hw_state pointer |
| @ Load the saved state back into the VFP |
| VFPFLDMIA r10, r5 @ reload the working registers while |
| @ FPEXC is in a safe state |
| ldmia r10, {r1, r5, r6, r8} @ load FPEXC, FPSCR, FPINST, FPINST2 |
| #ifndef CONFIG_CPU_FEROCEON |
| tst r1, #FPEXC_EX @ is there additional state to restore? |
| beq 1f |
| VFPFMXR FPINST, r6 @ restore FPINST (only if FPEXC.EX is set) |
| tst r1, #FPEXC_FP2V @ is there an FPINST2 to write? |
| beq 1f |
| VFPFMXR FPINST2, r8 @ FPINST2 if needed (and present) |
| 1: |
| #endif |
| VFPFMXR FPSCR, r5 @ restore status |
| |
| @ The context stored in the VFP hardware is up to date with this thread |
| vfp_hw_state_valid: |
| tst r1, #FPEXC_EX |
| bne process_exception @ might as well handle the pending |
| @ exception before retrying branch |
| @ out before setting an FPEXC that |
| @ stops us reading stuff |
| VFPFMXR FPEXC, r1 @ Restore FPEXC last |
| mov sp, r3 @ we think we have handled things |
| pop {lr} |
| sub r2, r2, #4 @ Retry current instruction - if Thumb |
| str r2, [sp, #S_PC] @ mode it's two 16-bit instructions, |
| @ else it's one 32-bit instruction, so |
| @ always subtract 4 from the following |
| @ instruction address. |
| |
| local_bh_enable_and_ret: |
| adr r0, . |
| mov r1, #SOFTIRQ_DISABLE_OFFSET |
| b __local_bh_enable_ip @ tail call |
| |
| look_for_VFP_exceptions: |
| @ Check for synchronous or asynchronous exception |
| tst r1, #FPEXC_EX | FPEXC_DEX |
| bne process_exception |
| @ On some implementations of the VFP subarch 1, setting FPSCR.IXE |
| @ causes all the CDP instructions to be bounced synchronously without |
| @ setting the FPEXC.EX bit |
| VFPFMRX r5, FPSCR |
| tst r5, #FPSCR_IXE |
| bne process_exception |
| |
| tst r5, #FPSCR_LENGTH_MASK |
| beq skip |
| orr r1, r1, #FPEXC_DEX |
| b process_exception |
| skip: |
| |
| @ Fall into hand on to next handler - appropriate coproc instr |
| @ not recognised by VFP |
| |
| DBGSTR "not VFP" |
| b local_bh_enable_and_ret |
| |
| process_exception: |
| DBGSTR "bounce" |
| mov sp, r3 @ setup for a return to the user code. |
| pop {lr} |
| mov r2, sp @ nothing stacked - regdump is at TOS |
| |
| @ Now call the C code to package up the bounce to the support code |
| @ r0 holds the trigger instruction |
| @ r1 holds the FPEXC value |
| @ r2 pointer to register dump |
| b VFP_bounce @ we have handled this - the support |
| @ code will raise an exception if |
| @ required. If not, the user code will |
| @ retry the faulted instruction |
| ENDPROC(vfp_support_entry) |
| |
| ENTRY(vfp_save_state) |
| @ Save the current VFP state |
| @ r0 - save location |
| @ r1 - FPEXC |
| DBGSTR1 "save VFP state %p", r0 |
| VFPFSTMIA r0, r2 @ save the working registers |
| VFPFMRX r2, FPSCR @ current status |
| tst r1, #FPEXC_EX @ is there additional state to save? |
| beq 1f |
| VFPFMRX r3, FPINST @ FPINST (only if FPEXC.EX is set) |
| tst r1, #FPEXC_FP2V @ is there an FPINST2 to read? |
| beq 1f |
| VFPFMRX r12, FPINST2 @ FPINST2 if needed (and present) |
| 1: |
| stmia r0, {r1, r2, r3, r12} @ save FPEXC, FPSCR, FPINST, FPINST2 |
| ret lr |
| ENDPROC(vfp_save_state) |
| |
| .align |
| vfp_current_hw_state_address: |
| .word vfp_current_hw_state |
| |
| .macro tbl_branch, base, tmp, shift |
| #ifdef CONFIG_THUMB2_KERNEL |
| adr \tmp, 1f |
| add \tmp, \tmp, \base, lsl \shift |
| ret \tmp |
| #else |
| add pc, pc, \base, lsl \shift |
| mov r0, r0 |
| #endif |
| 1: |
| .endm |
| |
| ENTRY(vfp_get_float) |
| tbl_branch r0, r3, #3 |
| .fpu vfpv2 |
| .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 |
| 1: vmov r0, s\dr |
| ret lr |
| .org 1b + 8 |
| .endr |
| .irp dr,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 |
| 1: vmov r0, s\dr |
| ret lr |
| .org 1b + 8 |
| .endr |
| ENDPROC(vfp_get_float) |
| |
| ENTRY(vfp_put_float) |
| tbl_branch r1, r3, #3 |
| .fpu vfpv2 |
| .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 |
| 1: vmov s\dr, r0 |
| ret lr |
| .org 1b + 8 |
| .endr |
| .irp dr,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 |
| 1: vmov s\dr, r0 |
| ret lr |
| .org 1b + 8 |
| .endr |
| ENDPROC(vfp_put_float) |
| |
| ENTRY(vfp_get_double) |
| tbl_branch r0, r3, #3 |
| .fpu vfpv2 |
| .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 |
| 1: vmov r0, r1, d\dr |
| ret lr |
| .org 1b + 8 |
| .endr |
| #ifdef CONFIG_VFPv3 |
| @ d16 - d31 registers |
| .fpu vfpv3 |
| .irp dr,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 |
| 1: vmov r0, r1, d\dr |
| ret lr |
| .org 1b + 8 |
| .endr |
| #endif |
| |
| @ virtual register 16 (or 32 if VFPv3) for compare with zero |
| mov r0, #0 |
| mov r1, #0 |
| ret lr |
| ENDPROC(vfp_get_double) |
| |
| ENTRY(vfp_put_double) |
| tbl_branch r2, r3, #3 |
| .fpu vfpv2 |
| .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 |
| 1: vmov d\dr, r0, r1 |
| ret lr |
| .org 1b + 8 |
| .endr |
| #ifdef CONFIG_VFPv3 |
| .fpu vfpv3 |
| @ d16 - d31 registers |
| .irp dr,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 |
| 1: vmov d\dr, r0, r1 |
| ret lr |
| .org 1b + 8 |
| .endr |
| #endif |
| ENDPROC(vfp_put_double) |