| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * FP/SIMD context switching and fault handling | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2012 ARM Ltd. | 
					
						
							|  |  |  |  * Author: Catalin Marinas <catalin.marinas@arm.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License version 2 as | 
					
						
							|  |  |  |  * published by the Free Software Foundation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-19 17:48:08 +01:00
										 |  |  | #include <linux/cpu_pm.h>
 | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/signal.h>
 | 
					
						
							| 
									
										
										
										
											2013-07-09 14:18:12 +01:00
										 |  |  | #include <linux/hardirq.h>
 | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <asm/fpsimd.h>
 | 
					
						
							|  |  |  | #include <asm/cputype.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define FPEXC_IOF	(1 << 0)
 | 
					
						
							|  |  |  | #define FPEXC_DZF	(1 << 1)
 | 
					
						
							|  |  |  | #define FPEXC_OFF	(1 << 2)
 | 
					
						
							|  |  |  | #define FPEXC_UFF	(1 << 3)
 | 
					
						
							|  |  |  | #define FPEXC_IXF	(1 << 4)
 | 
					
						
							|  |  |  | #define FPEXC_IDF	(1 << 7)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Trapped FP/ASIMD access. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void do_fpsimd_acc(unsigned int esr, struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* TODO: implement lazy context saving/restoring */ | 
					
						
							|  |  |  | 	WARN_ON(1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Raise a SIGFPE for the current process. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	siginfo_t info; | 
					
						
							|  |  |  | 	unsigned int si_code = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (esr & FPEXC_IOF) | 
					
						
							|  |  |  | 		si_code = FPE_FLTINV; | 
					
						
							|  |  |  | 	else if (esr & FPEXC_DZF) | 
					
						
							|  |  |  | 		si_code = FPE_FLTDIV; | 
					
						
							|  |  |  | 	else if (esr & FPEXC_OFF) | 
					
						
							|  |  |  | 		si_code = FPE_FLTOVF; | 
					
						
							|  |  |  | 	else if (esr & FPEXC_UFF) | 
					
						
							|  |  |  | 		si_code = FPE_FLTUND; | 
					
						
							|  |  |  | 	else if (esr & FPEXC_IXF) | 
					
						
							|  |  |  | 		si_code = FPE_FLTRES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(&info, 0, sizeof(info)); | 
					
						
							|  |  |  | 	info.si_signo = SIGFPE; | 
					
						
							|  |  |  | 	info.si_code = si_code; | 
					
						
							|  |  |  | 	info.si_addr = (void __user *)instruction_pointer(regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	send_sig_info(SIGFPE, &info, current); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void fpsimd_thread_switch(struct task_struct *next) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* check if not kernel threads */ | 
					
						
							|  |  |  | 	if (current->mm) | 
					
						
							|  |  |  | 		fpsimd_save_state(¤t->thread.fpsimd_state); | 
					
						
							|  |  |  | 	if (next->mm) | 
					
						
							|  |  |  | 		fpsimd_load_state(&next->thread.fpsimd_state); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void fpsimd_flush_thread(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-09-27 09:04:41 +01:00
										 |  |  | 	preempt_disable(); | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | 	memset(¤t->thread.fpsimd_state, 0, sizeof(struct fpsimd_state)); | 
					
						
							|  |  |  | 	fpsimd_load_state(¤t->thread.fpsimd_state); | 
					
						
							| 
									
										
										
										
											2013-09-27 09:04:41 +01:00
										 |  |  | 	preempt_enable(); | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-09 14:18:12 +01:00
										 |  |  | #ifdef CONFIG_KERNEL_MODE_NEON
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Kernel-side NEON support functions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void kernel_neon_begin(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Avoid using the NEON in interrupt context */ | 
					
						
							|  |  |  | 	BUG_ON(in_interrupt()); | 
					
						
							|  |  |  | 	preempt_disable(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (current->mm) | 
					
						
							|  |  |  | 		fpsimd_save_state(¤t->thread.fpsimd_state); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(kernel_neon_begin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void kernel_neon_end(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (current->mm) | 
					
						
							|  |  |  | 		fpsimd_load_state(¤t->thread.fpsimd_state); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	preempt_enable(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(kernel_neon_end); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif /* CONFIG_KERNEL_MODE_NEON */
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-19 17:48:08 +01:00
										 |  |  | #ifdef CONFIG_CPU_PM
 | 
					
						
							|  |  |  | static int fpsimd_cpu_pm_notifier(struct notifier_block *self, | 
					
						
							|  |  |  | 				  unsigned long cmd, void *v) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case CPU_PM_ENTER: | 
					
						
							|  |  |  | 		if (current->mm) | 
					
						
							|  |  |  | 			fpsimd_save_state(¤t->thread.fpsimd_state); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case CPU_PM_EXIT: | 
					
						
							|  |  |  | 		if (current->mm) | 
					
						
							|  |  |  | 			fpsimd_load_state(¤t->thread.fpsimd_state); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case CPU_PM_ENTER_FAILED: | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return NOTIFY_DONE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return NOTIFY_OK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct notifier_block fpsimd_cpu_pm_notifier_block = { | 
					
						
							|  |  |  | 	.notifier_call = fpsimd_cpu_pm_notifier, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void fpsimd_pm_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	cpu_pm_register_notifier(&fpsimd_cpu_pm_notifier_block); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | static inline void fpsimd_pm_init(void) { } | 
					
						
							|  |  |  | #endif /* CONFIG_CPU_PM */
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * FP/SIMD support code initialisation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int __init fpsimd_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u64 pfr = read_cpuid(ID_AA64PFR0_EL1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pfr & (0xf << 16)) { | 
					
						
							|  |  |  | 		pr_notice("Floating-point is not implemented\n"); | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	elf_hwcap |= HWCAP_FP; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (pfr & (0xf << 20)) | 
					
						
							|  |  |  | 		pr_notice("Advanced SIMD is not implemented\n"); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		elf_hwcap |= HWCAP_ASIMD; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-19 17:48:08 +01:00
										 |  |  | 	fpsimd_pm_init(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:32 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | late_initcall(fpsimd_init); |