 fb1ab1ab38
			
		
	
	
	fb1ab1ab38
	
	
	
		
			
			When a CPU enters a low power state, its FP register content is lost. This patch adds a notifier to save the FP context on CPU shutdown and restore it on CPU resume. The context is saved and restored only if the suspending thread is not a kernel thread, mirroring the current context switch behaviour. Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
		
			
				
	
	
		
			172 lines
		
	
	
	
		
			3.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
	
		
			3.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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/>.
 | |
|  */
 | |
| 
 | |
| #include <linux/cpu_pm.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/sched.h>
 | |
| #include <linux/signal.h>
 | |
| #include <linux/hardirq.h>
 | |
| 
 | |
| #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)
 | |
| {
 | |
| 	preempt_disable();
 | |
| 	memset(¤t->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
 | |
| 	fpsimd_load_state(¤t->thread.fpsimd_state);
 | |
| 	preempt_enable();
 | |
| }
 | |
| 
 | |
| #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 */
 | |
| 
 | |
| #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 */
 | |
| 
 | |
| /*
 | |
|  * 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;
 | |
| 
 | |
| 	fpsimd_pm_init();
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| late_initcall(fpsimd_init);
 |