|  | // 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); | 
|  | } |