xref: /openbmc/linux/arch/arm64/kernel/fpsimd.c (revision b34e08d5)
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(&current->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(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
86 	fpsimd_load_state(&current->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(&current->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(&current->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(&current->thread.fpsimd_state);
125 		break;
126 	case CPU_PM_EXIT:
127 		if (current->mm)
128 			fpsimd_load_state(&current->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