1 /* 2 * FP/SIMD context switching and fault handling 3 * 4 * Copyright (C) 2012 ARM Ltd. 5 * Author: Catalin Marinas <catalin.marinas@arm.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include <linux/cpu_pm.h> 21 #include <linux/kernel.h> 22 #include <linux/init.h> 23 #include <linux/sched.h> 24 #include <linux/signal.h> 25 #include <linux/hardirq.h> 26 27 #include <asm/fpsimd.h> 28 #include <asm/cputype.h> 29 30 #define FPEXC_IOF (1 << 0) 31 #define FPEXC_DZF (1 << 1) 32 #define FPEXC_OFF (1 << 2) 33 #define FPEXC_UFF (1 << 3) 34 #define FPEXC_IXF (1 << 4) 35 #define FPEXC_IDF (1 << 7) 36 37 /* 38 * Trapped FP/ASIMD access. 39 */ 40 void do_fpsimd_acc(unsigned int esr, struct pt_regs *regs) 41 { 42 /* TODO: implement lazy context saving/restoring */ 43 WARN_ON(1); 44 } 45 46 /* 47 * Raise a SIGFPE for the current process. 48 */ 49 void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs) 50 { 51 siginfo_t info; 52 unsigned int si_code = 0; 53 54 if (esr & FPEXC_IOF) 55 si_code = FPE_FLTINV; 56 else if (esr & FPEXC_DZF) 57 si_code = FPE_FLTDIV; 58 else if (esr & FPEXC_OFF) 59 si_code = FPE_FLTOVF; 60 else if (esr & FPEXC_UFF) 61 si_code = FPE_FLTUND; 62 else if (esr & FPEXC_IXF) 63 si_code = FPE_FLTRES; 64 65 memset(&info, 0, sizeof(info)); 66 info.si_signo = SIGFPE; 67 info.si_code = si_code; 68 info.si_addr = (void __user *)instruction_pointer(regs); 69 70 send_sig_info(SIGFPE, &info, current); 71 } 72 73 void fpsimd_thread_switch(struct task_struct *next) 74 { 75 /* check if not kernel threads */ 76 if (current->mm) 77 fpsimd_save_state(¤t->thread.fpsimd_state); 78 if (next->mm) 79 fpsimd_load_state(&next->thread.fpsimd_state); 80 } 81 82 void fpsimd_flush_thread(void) 83 { 84 preempt_disable(); 85 memset(¤t->thread.fpsimd_state, 0, sizeof(struct fpsimd_state)); 86 fpsimd_load_state(¤t->thread.fpsimd_state); 87 preempt_enable(); 88 } 89 90 #ifdef CONFIG_KERNEL_MODE_NEON 91 92 /* 93 * Kernel-side NEON support functions 94 */ 95 void kernel_neon_begin(void) 96 { 97 /* Avoid using the NEON in interrupt context */ 98 BUG_ON(in_interrupt()); 99 preempt_disable(); 100 101 if (current->mm) 102 fpsimd_save_state(¤t->thread.fpsimd_state); 103 } 104 EXPORT_SYMBOL(kernel_neon_begin); 105 106 void kernel_neon_end(void) 107 { 108 if (current->mm) 109 fpsimd_load_state(¤t->thread.fpsimd_state); 110 111 preempt_enable(); 112 } 113 EXPORT_SYMBOL(kernel_neon_end); 114 115 #endif /* CONFIG_KERNEL_MODE_NEON */ 116 117 #ifdef CONFIG_CPU_PM 118 static int fpsimd_cpu_pm_notifier(struct notifier_block *self, 119 unsigned long cmd, void *v) 120 { 121 switch (cmd) { 122 case CPU_PM_ENTER: 123 if (current->mm) 124 fpsimd_save_state(¤t->thread.fpsimd_state); 125 break; 126 case CPU_PM_EXIT: 127 if (current->mm) 128 fpsimd_load_state(¤t->thread.fpsimd_state); 129 break; 130 case CPU_PM_ENTER_FAILED: 131 default: 132 return NOTIFY_DONE; 133 } 134 return NOTIFY_OK; 135 } 136 137 static struct notifier_block fpsimd_cpu_pm_notifier_block = { 138 .notifier_call = fpsimd_cpu_pm_notifier, 139 }; 140 141 static void fpsimd_pm_init(void) 142 { 143 cpu_pm_register_notifier(&fpsimd_cpu_pm_notifier_block); 144 } 145 146 #else 147 static inline void fpsimd_pm_init(void) { } 148 #endif /* CONFIG_CPU_PM */ 149 150 /* 151 * FP/SIMD support code initialisation. 152 */ 153 static int __init fpsimd_init(void) 154 { 155 u64 pfr = read_cpuid(ID_AA64PFR0_EL1); 156 157 if (pfr & (0xf << 16)) { 158 pr_notice("Floating-point is not implemented\n"); 159 return 0; 160 } 161 elf_hwcap |= HWCAP_FP; 162 163 if (pfr & (0xf << 20)) 164 pr_notice("Advanced SIMD is not implemented\n"); 165 else 166 elf_hwcap |= HWCAP_ASIMD; 167 168 fpsimd_pm_init(); 169 170 return 0; 171 } 172 late_initcall(fpsimd_init); 173