ARM: vfp: Use undef hook for handling VFP exceptions
authorArd Biesheuvel <ardb@kernel.org>
Sat, 18 Mar 2023 23:28:35 +0000 (00:28 +0100)
committerArd Biesheuvel <ardb@kernel.org>
Wed, 17 May 2023 13:08:22 +0000 (15:08 +0200)
Now that the VFP support code has been reimplemented as a C function
that takes a struct pt_regs pointer and an opcode, we can use the
existing undef_hook framework to deal with undef exceptions triggered by
VFP instructions instead of having special handling in assembler.

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
arch/arm/kernel/entry-armv.S
arch/arm/vfp/Makefile
arch/arm/vfp/entry.S [deleted file]
arch/arm/vfp/vfpmodule.c

index ba47f6aac5ff8692f337ba45082985580372c0cc..0e40b2566f598b832ba80b2fcc563fbfbc89a58c 100644 (file)
@@ -557,13 +557,6 @@ ENDPROC(__und_usr)
  * co-processor instructions.  However, we have to watch out
  * for the ARM6/ARM7 SWI bug.
  *
- * NEON is a special case that has to be handled here. Not all
- * NEON instructions are co-processor instructions, so we have
- * to make a special case of checking for them. Plus, there's
- * five groups of them, so we have a table of mask/opcode pairs
- * to check against, and if any match then we branch off into the
- * NEON handler code.
- *
  * Emulators may wish to make use of the following registers:
  *  r0  = instruction opcode (32-bit ARM or two 16-bit Thumb)
  *  r2  = PC value to resume execution after successful emulation
@@ -575,25 +568,8 @@ ENDPROC(__und_usr)
        @
        @ Fall-through from Thumb-2 __und_usr
        @
-#ifdef CONFIG_NEON
-       get_thread_info r10                     @ get current thread
-       adr     r6, .LCneon_thumb_opcodes
-       b       2f
-#endif
 call_fpe:
        get_thread_info r10                     @ get current thread
-#ifdef CONFIG_NEON
-       adr     r6, .LCneon_arm_opcodes
-2:     ldr     r5, [r6], #4                    @ mask value
-       ldr     r7, [r6], #4                    @ opcode bits matching in mask
-       cmp     r5, #0                          @ end mask?
-       beq     1f
-       and     r8, r0, r5
-       cmp     r8, r7                          @ NEON instruction?
-       bne     2b
-       b       do_vfp                          @ let VFP handler handle this
-1:
-#endif
        tst     r0, #0x08000000                 @ only CDP/CPRT/LDC/STC have bit 27
        tstne   r0, #0x04000000                 @ bit 26 set on both ARM and Thumb-2
        reteq   lr
@@ -620,42 +596,13 @@ call_fpe:
        ret.w   lr                              @ CP#7
        ret.w   lr                              @ CP#8
        ret.w   lr                              @ CP#9
-#ifdef CONFIG_VFP
-       W(b)    do_vfp                          @ CP#10 (VFP)
-       W(b)    do_vfp                          @ CP#11 (VFP)
-#else
        ret.w   lr                              @ CP#10 (VFP)
        ret.w   lr                              @ CP#11 (VFP)
-#endif
        ret.w   lr                              @ CP#12
        ret.w   lr                              @ CP#13
        ret.w   lr                              @ CP#14 (Debug)
        ret.w   lr                              @ CP#15 (Control)
 
-#ifdef CONFIG_NEON
-       .align  6
-
-.LCneon_arm_opcodes:
-       .word   0xfe000000                      @ mask
-       .word   0xf2000000                      @ opcode
-
-       .word   0xff100000                      @ mask
-       .word   0xf4000000                      @ opcode
-
-       .word   0x00000000                      @ mask
-       .word   0x00000000                      @ opcode
-
-.LCneon_thumb_opcodes:
-       .word   0xef000000                      @ mask
-       .word   0xef000000                      @ opcode
-
-       .word   0xff100000                      @ mask
-       .word   0xf9000000                      @ opcode
-
-       .word   0x00000000                      @ mask
-       .word   0x00000000                      @ opcode
-#endif
-
 do_fpe:
        add     r10, r10, #TI_FPSTATE           @ r10 = workspace
        ldr_va  pc, fp_enter, tmp=r4            @ Call FP module USR entry point
index 749901a72d6dc6c4beffddc464a41ba0a5173e8a..dfd64bc2b2fbdd06106b1bc9ed2031ba35391b67 100644 (file)
@@ -8,4 +8,4 @@
 # ccflags-y := -DDEBUG
 # asflags-y := -DDEBUG
 
-obj-y          += vfpmodule.o entry.o vfphw.o vfpsingle.o vfpdouble.o
+obj-y          += vfpmodule.o vfphw.o vfpsingle.o vfpdouble.o
diff --git a/arch/arm/vfp/entry.S b/arch/arm/vfp/entry.S
deleted file mode 100644 (file)
index 547c94c..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- *  linux/arch/arm/vfp/entry.S
- *
- *  Copyright (C) 2004 ARM Limited.
- *  Written by Deep Blue Solutions Limited.
- */
-#include <linux/init.h>
-#include <linux/linkage.h>
-#include <asm/thread_info.h>
-#include <asm/vfpmacros.h>
-#include <asm/assembler.h>
-#include <asm/asm-offsets.h>
-
-@ VFP entry point.
-@
-@  r0  = instruction opcode (32-bit ARM or two 16-bit Thumb)
-@  r2  = PC value to resume execution after successful emulation
-@  r9  = normal "successful" return address
-@  r10 = this threads thread_info structure
-@  lr  = unrecognised instruction return address
-@  IRQs enabled.
-@
-ENTRY(do_vfp)
-       mov     r1, r0                          @ pass trigger opcode via R1
-       mov     r0, sp                          @ pass struct pt_regs via R0
-       bl      vfp_support_entry               @ dispatch the VFP exception
-       cmp     r0, #0                          @ handled successfully?
-       reteq   r9                              @ then use R9 as return address
-       ret     lr                              @ pass to undef handler
-ENDPROC(do_vfp)
index 7572cb5b28a2eb084c25cd0d44a3028397f06b7f..58a9442add24b2272ccb942bfabd469cf7b30f7b 100644 (file)
@@ -641,15 +641,37 @@ static int vfp_starting_cpu(unsigned int unused)
        return 0;
 }
 
+static int vfp_kmode_exception(struct pt_regs *regs, unsigned int instr)
+{
+       /*
+        * If we reach this point, a floating point exception has been raised
+        * while running in kernel mode. If the NEON/VFP unit was enabled at the
+        * time, it means a VFP instruction has been issued that requires
+        * software assistance to complete, something which is not currently
+        * supported in kernel mode.
+        * If the NEON/VFP unit was disabled, and the location pointed to below
+        * is properly preceded by a call to kernel_neon_begin(), something has
+        * caused the task to be scheduled out and back in again. In this case,
+        * rebuilding and running with CONFIG_DEBUG_ATOMIC_SLEEP enabled should
+        * be helpful in localizing the problem.
+        */
+       if (fmrx(FPEXC) & FPEXC_EN)
+               pr_crit("BUG: unsupported FP instruction in kernel mode\n");
+       else
+               pr_crit("BUG: FP instruction issued in kernel mode with FP unit disabled\n");
+       pr_crit("FPEXC == 0x%08x\n", fmrx(FPEXC));
+       return 1;
+}
+
 /*
- * vfp_support_entry - Handle VFP exception from user mode
+ * vfp_support_entry - Handle VFP exception
  *
  * @regs:      pt_regs structure holding the register state at exception entry
  * @trigger:   The opcode of the instruction that triggered the exception
  *
  * Returns 0 if the exception was handled, or an error code otherwise.
  */
-asmlinkage int vfp_support_entry(struct pt_regs *regs, u32 trigger)
+static int vfp_support_entry(struct pt_regs *regs, u32 trigger)
 {
        struct thread_info *ti = current_thread_info();
        u32 fpexc;
@@ -657,6 +679,9 @@ asmlinkage int vfp_support_entry(struct pt_regs *regs, u32 trigger)
        if (unlikely(!have_vfp))
                return -ENODEV;
 
+       if (!user_mode(regs))
+               return vfp_kmode_exception(regs, trigger);
+
        local_bh_disable();
        fpexc = fmrx(FPEXC);
 
@@ -722,7 +747,6 @@ asmlinkage int vfp_support_entry(struct pt_regs *regs, u32 trigger)
                 * replay the instruction that trapped.
                 */
                fmxr(FPEXC, fpexc);
-               regs->ARM_pc -= 4;
        } else {
                /* Check for synchronous or asynchronous exceptions */
                if (!(fpexc & (FPEXC_EX | FPEXC_DEX))) {
@@ -743,78 +767,47 @@ asmlinkage int vfp_support_entry(struct pt_regs *regs, u32 trigger)
                                fpexc |= FPEXC_DEX;
                        }
                }
-bounce:                VFP_bounce(trigger, fpexc, regs);
+bounce:                regs->ARM_pc += 4;
+               VFP_bounce(trigger, fpexc, regs);
        }
 
        local_bh_enable();
        return 0;
 }
 
-#ifdef CONFIG_KERNEL_MODE_NEON
-
-static int vfp_kmode_exception(struct pt_regs *regs, unsigned int instr)
-{
-       /*
-        * If we reach this point, a floating point exception has been raised
-        * while running in kernel mode. If the NEON/VFP unit was enabled at the
-        * time, it means a VFP instruction has been issued that requires
-        * software assistance to complete, something which is not currently
-        * supported in kernel mode.
-        * If the NEON/VFP unit was disabled, and the location pointed to below
-        * is properly preceded by a call to kernel_neon_begin(), something has
-        * caused the task to be scheduled out and back in again. In this case,
-        * rebuilding and running with CONFIG_DEBUG_ATOMIC_SLEEP enabled should
-        * be helpful in localizing the problem.
-        */
-       if (fmrx(FPEXC) & FPEXC_EN)
-               pr_crit("BUG: unsupported FP instruction in kernel mode\n");
-       else
-               pr_crit("BUG: FP instruction issued in kernel mode with FP unit disabled\n");
-       pr_crit("FPEXC == 0x%08x\n", fmrx(FPEXC));
-       return 1;
-}
-
-static struct undef_hook vfp_kmode_exception_hook[] = {{
+static struct undef_hook neon_support_hook[] = {{
        .instr_mask     = 0xfe000000,
        .instr_val      = 0xf2000000,
-       .cpsr_mask      = MODE_MASK | PSR_T_BIT,
-       .cpsr_val       = SVC_MODE,
-       .fn             = vfp_kmode_exception,
+       .cpsr_mask      = PSR_T_BIT,
+       .cpsr_val       = 0,
+       .fn             = vfp_support_entry,
 }, {
        .instr_mask     = 0xff100000,
        .instr_val      = 0xf4000000,
-       .cpsr_mask      = MODE_MASK | PSR_T_BIT,
-       .cpsr_val       = SVC_MODE,
-       .fn             = vfp_kmode_exception,
+       .cpsr_mask      = PSR_T_BIT,
+       .cpsr_val       = 0,
+       .fn             = vfp_support_entry,
 }, {
        .instr_mask     = 0xef000000,
        .instr_val      = 0xef000000,
-       .cpsr_mask      = MODE_MASK | PSR_T_BIT,
-       .cpsr_val       = SVC_MODE | PSR_T_BIT,
-       .fn             = vfp_kmode_exception,
+       .cpsr_mask      = PSR_T_BIT,
+       .cpsr_val       = PSR_T_BIT,
+       .fn             = vfp_support_entry,
 }, {
        .instr_mask     = 0xff100000,
        .instr_val      = 0xf9000000,
-       .cpsr_mask      = MODE_MASK | PSR_T_BIT,
-       .cpsr_val       = SVC_MODE | PSR_T_BIT,
-       .fn             = vfp_kmode_exception,
-}, {
-       .instr_mask     = 0x0c000e00,
-       .instr_val      = 0x0c000a00,
-       .cpsr_mask      = MODE_MASK,
-       .cpsr_val       = SVC_MODE,
-       .fn             = vfp_kmode_exception,
+       .cpsr_mask      = PSR_T_BIT,
+       .cpsr_val       = PSR_T_BIT,
+       .fn             = vfp_support_entry,
 }};
 
-static int __init vfp_kmode_exception_hook_init(void)
-{
-       int i;
+static struct undef_hook vfp_support_hook = {
+       .instr_mask     = 0x0c000e00,
+       .instr_val      = 0x0c000a00,
+       .fn             = vfp_support_entry,
+};
 
-       for (i = 0; i < ARRAY_SIZE(vfp_kmode_exception_hook); i++)
-               register_undef_hook(&vfp_kmode_exception_hook[i]);
-       return 0;
-}
-subsys_initcall(vfp_kmode_exception_hook_init);
+#ifdef CONFIG_KERNEL_MODE_NEON
 
 /*
  * Kernel-side NEON support functions
@@ -919,8 +912,11 @@ static int __init vfp_init(void)
                 * for NEON if the hardware has the MVFR registers.
                 */
                if (IS_ENABLED(CONFIG_NEON) &&
-                  (fmrx(MVFR1) & 0x000fff00) == 0x00011100)
+                   (fmrx(MVFR1) & 0x000fff00) == 0x00011100) {
                        elf_hwcap |= HWCAP_NEON;
+                       for (int i = 0; i < ARRAY_SIZE(neon_support_hook); i++)
+                               register_undef_hook(&neon_support_hook[i]);
+               }
 
                if (IS_ENABLED(CONFIG_VFPv3)) {
                        u32 mvfr0 = fmrx(MVFR0);
@@ -989,6 +985,7 @@ static int __init vfp_init(void)
 
        have_vfp = true;
 
+       register_undef_hook(&vfp_support_hook);
        thread_register_notifier(&vfp_notifier_block);
        vfp_pm_init();