| // SPDX-License-Identifier: GPL-2.0 | 
 | // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. | 
 |  | 
 | #include <linux/ptrace.h> | 
 | #include <linux/uaccess.h> | 
 | #include <abi/reg_ops.h> | 
 |  | 
 | #define MTCR_MASK	0xFC00FFE0 | 
 | #define MFCR_MASK	0xFC00FFE0 | 
 | #define MTCR_DIST	0xC0006420 | 
 | #define MFCR_DIST	0xC0006020 | 
 |  | 
 | /* | 
 |  * fpu_libc_helper() is to help libc to excute: | 
 |  *  - mfcr %a, cr<1, 2> | 
 |  *  - mfcr %a, cr<2, 2> | 
 |  *  - mtcr %a, cr<1, 2> | 
 |  *  - mtcr %a, cr<2, 2> | 
 |  */ | 
 | int fpu_libc_helper(struct pt_regs *regs) | 
 | { | 
 | 	int fault; | 
 | 	unsigned long instrptr, regx = 0; | 
 | 	unsigned long index = 0, tmp = 0; | 
 | 	unsigned long tinstr = 0; | 
 | 	u16 instr_hi, instr_low; | 
 |  | 
 | 	instrptr = instruction_pointer(regs); | 
 | 	if (instrptr & 1) | 
 | 		return 0; | 
 |  | 
 | 	fault = __get_user(instr_low, (u16 *)instrptr); | 
 | 	if (fault) | 
 | 		return 0; | 
 |  | 
 | 	fault = __get_user(instr_hi, (u16 *)(instrptr + 2)); | 
 | 	if (fault) | 
 | 		return 0; | 
 |  | 
 | 	tinstr = instr_hi | ((unsigned long)instr_low << 16); | 
 |  | 
 | 	if (((tinstr >> 21) & 0x1F) != 2) | 
 | 		return 0; | 
 |  | 
 | 	if ((tinstr & MTCR_MASK) == MTCR_DIST) { | 
 | 		index = (tinstr >> 16) & 0x1F; | 
 | 		if (index > 13) | 
 | 			return 0; | 
 |  | 
 | 		tmp = tinstr & 0x1F; | 
 | 		if (tmp > 2) | 
 | 			return 0; | 
 |  | 
 | 		regx =  *(®s->a0 + index); | 
 |  | 
 | 		if (tmp == 1) | 
 | 			mtcr("cr<1, 2>", regx); | 
 | 		else if (tmp == 2) | 
 | 			mtcr("cr<2, 2>", regx); | 
 | 		else | 
 | 			return 0; | 
 |  | 
 | 		regs->pc += 4; | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	if ((tinstr & MFCR_MASK) == MFCR_DIST) { | 
 | 		index = tinstr & 0x1F; | 
 | 		if (index > 13) | 
 | 			return 0; | 
 |  | 
 | 		tmp = ((tinstr >> 16) & 0x1F); | 
 | 		if (tmp > 2) | 
 | 			return 0; | 
 |  | 
 | 		if (tmp == 1) | 
 | 			regx = mfcr("cr<1, 2>"); | 
 | 		else if (tmp == 2) | 
 | 			regx = mfcr("cr<2, 2>"); | 
 | 		else | 
 | 			return 0; | 
 |  | 
 | 		*(®s->a0 + index) = regx; | 
 |  | 
 | 		regs->pc += 4; | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void fpu_fpe(struct pt_regs *regs) | 
 | { | 
 | 	int sig, code; | 
 | 	unsigned int fesr; | 
 |  | 
 | 	fesr = mfcr("cr<2, 2>"); | 
 |  | 
 | 	sig = SIGFPE; | 
 | 	code = FPE_FLTUNK; | 
 |  | 
 | 	if (fesr & FPE_ILLE) { | 
 | 		sig = SIGILL; | 
 | 		code = ILL_ILLOPC; | 
 | 	} else if (fesr & FPE_IDC) { | 
 | 		sig = SIGILL; | 
 | 		code = ILL_ILLOPN; | 
 | 	} else if (fesr & FPE_FEC) { | 
 | 		sig = SIGFPE; | 
 | 		if (fesr & FPE_IOC) | 
 | 			code = FPE_FLTINV; | 
 | 		else if (fesr & FPE_DZC) | 
 | 			code = FPE_FLTDIV; | 
 | 		else if (fesr & FPE_UFC) | 
 | 			code = FPE_FLTUND; | 
 | 		else if (fesr & FPE_OFC) | 
 | 			code = FPE_FLTOVF; | 
 | 		else if (fesr & FPE_IXC) | 
 | 			code = FPE_FLTRES; | 
 | 	} | 
 |  | 
 | 	force_sig_fault(sig, code, (void __user *)regs->pc); | 
 | } | 
 |  | 
 | #define FMFVR_FPU_REGS(vrx, vry)	\ | 
 | 	"fmfvrl %0, "#vrx"\n"		\ | 
 | 	"fmfvrh %1, "#vrx"\n"		\ | 
 | 	"fmfvrl %2, "#vry"\n"		\ | 
 | 	"fmfvrh %3, "#vry"\n" | 
 |  | 
 | #define FMTVR_FPU_REGS(vrx, vry)	\ | 
 | 	"fmtvrl "#vrx", %0\n"		\ | 
 | 	"fmtvrh "#vrx", %1\n"		\ | 
 | 	"fmtvrl "#vry", %2\n"		\ | 
 | 	"fmtvrh "#vry", %3\n" | 
 |  | 
 | #define STW_FPU_REGS(a, b, c, d)	\ | 
 | 	"stw    %0, (%4, "#a")\n"	\ | 
 | 	"stw    %1, (%4, "#b")\n"	\ | 
 | 	"stw    %2, (%4, "#c")\n"	\ | 
 | 	"stw    %3, (%4, "#d")\n" | 
 |  | 
 | #define LDW_FPU_REGS(a, b, c, d)	\ | 
 | 	"ldw    %0, (%4, "#a")\n"	\ | 
 | 	"ldw    %1, (%4, "#b")\n"	\ | 
 | 	"ldw    %2, (%4, "#c")\n"	\ | 
 | 	"ldw    %3, (%4, "#d")\n" | 
 |  | 
 | void save_to_user_fp(struct user_fp *user_fp) | 
 | { | 
 | 	unsigned long flg; | 
 | 	unsigned long tmp1, tmp2; | 
 | 	unsigned long *fpregs; | 
 |  | 
 | 	local_irq_save(flg); | 
 |  | 
 | 	tmp1 = mfcr("cr<1, 2>"); | 
 | 	tmp2 = mfcr("cr<2, 2>"); | 
 |  | 
 | 	user_fp->fcr = tmp1; | 
 | 	user_fp->fesr = tmp2; | 
 |  | 
 | 	fpregs = &user_fp->vr[0]; | 
 | #ifdef CONFIG_CPU_HAS_FPUV2 | 
 | #ifdef CONFIG_CPU_HAS_VDSP | 
 | 	asm volatile( | 
 | 		"vstmu.32    vr0-vr3,   (%0)\n" | 
 | 		"vstmu.32    vr4-vr7,   (%0)\n" | 
 | 		"vstmu.32    vr8-vr11,  (%0)\n" | 
 | 		"vstmu.32    vr12-vr15, (%0)\n" | 
 | 		"fstmu.64    vr16-vr31, (%0)\n" | 
 | 		: "+a"(fpregs) | 
 | 		::"memory"); | 
 | #else | 
 | 	asm volatile( | 
 | 		"fstmu.64    vr0-vr31,  (%0)\n" | 
 | 		: "+a"(fpregs) | 
 | 		::"memory"); | 
 | #endif | 
 | #else | 
 | 	{ | 
 | 	unsigned long tmp3, tmp4; | 
 |  | 
 | 	asm volatile( | 
 | 		FMFVR_FPU_REGS(vr0, vr1) | 
 | 		STW_FPU_REGS(0, 4, 16, 20) | 
 | 		FMFVR_FPU_REGS(vr2, vr3) | 
 | 		STW_FPU_REGS(32, 36, 48, 52) | 
 | 		FMFVR_FPU_REGS(vr4, vr5) | 
 | 		STW_FPU_REGS(64, 68, 80, 84) | 
 | 		FMFVR_FPU_REGS(vr6, vr7) | 
 | 		STW_FPU_REGS(96, 100, 112, 116) | 
 | 		"addi	%4, 128\n" | 
 | 		FMFVR_FPU_REGS(vr8, vr9) | 
 | 		STW_FPU_REGS(0, 4, 16, 20) | 
 | 		FMFVR_FPU_REGS(vr10, vr11) | 
 | 		STW_FPU_REGS(32, 36, 48, 52) | 
 | 		FMFVR_FPU_REGS(vr12, vr13) | 
 | 		STW_FPU_REGS(64, 68, 80, 84) | 
 | 		FMFVR_FPU_REGS(vr14, vr15) | 
 | 		STW_FPU_REGS(96, 100, 112, 116) | 
 | 		: "=a"(tmp1), "=a"(tmp2), "=a"(tmp3), | 
 | 		  "=a"(tmp4), "+a"(fpregs) | 
 | 		::"memory"); | 
 | 	} | 
 | #endif | 
 |  | 
 | 	local_irq_restore(flg); | 
 | } | 
 |  | 
 | void restore_from_user_fp(struct user_fp *user_fp) | 
 | { | 
 | 	unsigned long flg; | 
 | 	unsigned long tmp1, tmp2; | 
 | 	unsigned long *fpregs; | 
 |  | 
 | 	local_irq_save(flg); | 
 |  | 
 | 	tmp1 = user_fp->fcr; | 
 | 	tmp2 = user_fp->fesr; | 
 |  | 
 | 	mtcr("cr<1, 2>", tmp1); | 
 | 	mtcr("cr<2, 2>", tmp2); | 
 |  | 
 | 	fpregs = &user_fp->vr[0]; | 
 | #ifdef CONFIG_CPU_HAS_FPUV2 | 
 | #ifdef CONFIG_CPU_HAS_VDSP | 
 | 	asm volatile( | 
 | 		"vldmu.32    vr0-vr3,   (%0)\n" | 
 | 		"vldmu.32    vr4-vr7,   (%0)\n" | 
 | 		"vldmu.32    vr8-vr11,  (%0)\n" | 
 | 		"vldmu.32    vr12-vr15, (%0)\n" | 
 | 		"fldmu.64    vr16-vr31, (%0)\n" | 
 | 		: "+a"(fpregs) | 
 | 		::"memory"); | 
 | #else | 
 | 	asm volatile( | 
 | 		"fldmu.64    vr0-vr31,  (%0)\n" | 
 | 		: "+a"(fpregs) | 
 | 		::"memory"); | 
 | #endif | 
 | #else | 
 | 	{ | 
 | 	unsigned long tmp3, tmp4; | 
 |  | 
 | 	asm volatile( | 
 | 		LDW_FPU_REGS(0, 4, 16, 20) | 
 | 		FMTVR_FPU_REGS(vr0, vr1) | 
 | 		LDW_FPU_REGS(32, 36, 48, 52) | 
 | 		FMTVR_FPU_REGS(vr2, vr3) | 
 | 		LDW_FPU_REGS(64, 68, 80, 84) | 
 | 		FMTVR_FPU_REGS(vr4, vr5) | 
 | 		LDW_FPU_REGS(96, 100, 112, 116) | 
 | 		FMTVR_FPU_REGS(vr6, vr7) | 
 | 		"addi	%4, 128\n" | 
 | 		LDW_FPU_REGS(0, 4, 16, 20) | 
 | 		FMTVR_FPU_REGS(vr8, vr9) | 
 | 		LDW_FPU_REGS(32, 36, 48, 52) | 
 | 		FMTVR_FPU_REGS(vr10, vr11) | 
 | 		LDW_FPU_REGS(64, 68, 80, 84) | 
 | 		FMTVR_FPU_REGS(vr12, vr13) | 
 | 		LDW_FPU_REGS(96, 100, 112, 116) | 
 | 		FMTVR_FPU_REGS(vr14, vr15) | 
 | 		: "=a"(tmp1), "=a"(tmp2), "=a"(tmp3), | 
 | 		  "=a"(tmp4), "+a"(fpregs) | 
 | 		::"memory"); | 
 | 	} | 
 | #endif | 
 | 	local_irq_restore(flg); | 
 | } |