| // SPDX-License-Identifier: GPL-2.0+ | |
| #include <linux/bitops.h> | |
| #include <linux/kernel.h> | |
| #include <linux/kprobes.h> | |
| #include "decode-insn.h" | |
| #include "simulate-insn.h" | |
| static inline bool csky_insn_reg_get_val(struct pt_regs *regs, | |
| unsigned long index, | |
| unsigned long *ptr) | |
| { | |
| if (index < 14) | |
| *ptr = *(®s->a0 + index); | |
| if (index > 15 && index < 31) | |
| *ptr = *(®s->exregs[0] + index - 16); | |
| switch (index) { | |
| case 14: | |
| *ptr = regs->usp; | |
| break; | |
| case 15: | |
| *ptr = regs->lr; | |
| break; | |
| case 31: | |
| *ptr = regs->tls; | |
| break; | |
| default: | |
| goto fail; | |
| } | |
| return true; | |
| fail: | |
| return false; | |
| } | |
| static inline bool csky_insn_reg_set_val(struct pt_regs *regs, | |
| unsigned long index, | |
| unsigned long val) | |
| { | |
| if (index < 14) | |
| *(®s->a0 + index) = val; | |
| if (index > 15 && index < 31) | |
| *(®s->exregs[0] + index - 16) = val; | |
| switch (index) { | |
| case 14: | |
| regs->usp = val; | |
| break; | |
| case 15: | |
| regs->lr = val; | |
| break; | |
| case 31: | |
| regs->tls = val; | |
| break; | |
| default: | |
| goto fail; | |
| } | |
| return true; | |
| fail: | |
| return false; | |
| } | |
| void __kprobes | |
| simulate_br16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0x3ff) << 1, 9)); | |
| } | |
| void __kprobes | |
| simulate_br32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } | |
| void __kprobes | |
| simulate_bt16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| if (regs->sr & 1) | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0x3ff) << 1, 9)); | |
| else | |
| instruction_pointer_set(regs, addr + 2); | |
| } | |
| void __kprobes | |
| simulate_bt32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| if (regs->sr & 1) | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_bf16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| if (!(regs->sr & 1)) | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0x3ff) << 1, 9)); | |
| else | |
| instruction_pointer_set(regs, addr + 2); | |
| } | |
| void __kprobes | |
| simulate_bf32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| if (!(regs->sr & 1)) | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_jmp16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = (opcode >> 2) & 0xf; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| instruction_pointer_set(regs, tmp & 0xfffffffe); | |
| } | |
| void __kprobes | |
| simulate_jmp32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| instruction_pointer_set(regs, tmp & 0xfffffffe); | |
| } | |
| void __kprobes | |
| simulate_jsr16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = (opcode >> 2) & 0xf; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| regs->lr = addr + 2; | |
| instruction_pointer_set(regs, tmp & 0xfffffffe); | |
| } | |
| void __kprobes | |
| simulate_jsr32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| regs->lr = addr + 4; | |
| instruction_pointer_set(regs, tmp & 0xfffffffe); | |
| } | |
| void __kprobes | |
| simulate_lrw16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long val; | |
| unsigned long tmp = (opcode & 0x300) >> 3; | |
| unsigned long offset = ((opcode & 0x1f) | tmp) << 2; | |
| tmp = (opcode & 0xe0) >> 5; | |
| val = *(unsigned int *)(instruction_pointer(regs) + offset); | |
| csky_insn_reg_set_val(regs, tmp, val); | |
| } | |
| void __kprobes | |
| simulate_lrw32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long val; | |
| unsigned long offset = (opcode & 0xffff0000) >> 14; | |
| unsigned long tmp = opcode & 0x0000001f; | |
| val = *(unsigned int *) | |
| ((instruction_pointer(regs) + offset) & 0xfffffffc); | |
| csky_insn_reg_set_val(regs, tmp, val); | |
| } | |
| void __kprobes | |
| simulate_pop16(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long *tmp = (unsigned long *)regs->usp; | |
| int i; | |
| for (i = 0; i < (opcode & 0xf); i++) { | |
| csky_insn_reg_set_val(regs, i + 4, *tmp); | |
| tmp += 1; | |
| } | |
| if (opcode & 0x10) { | |
| csky_insn_reg_set_val(regs, 15, *tmp); | |
| tmp += 1; | |
| } | |
| regs->usp = (unsigned long)tmp; | |
| instruction_pointer_set(regs, regs->lr); | |
| } | |
| void __kprobes | |
| simulate_pop32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long *tmp = (unsigned long *)regs->usp; | |
| int i; | |
| for (i = 0; i < ((opcode & 0xf0000) >> 16); i++) { | |
| csky_insn_reg_set_val(regs, i + 4, *tmp); | |
| tmp += 1; | |
| } | |
| if (opcode & 0x100000) { | |
| csky_insn_reg_set_val(regs, 15, *tmp); | |
| tmp += 1; | |
| } | |
| for (i = 0; i < ((opcode & 0xe00000) >> 21); i++) { | |
| csky_insn_reg_set_val(regs, i + 16, *tmp); | |
| tmp += 1; | |
| } | |
| if (opcode & 0x1000000) { | |
| csky_insn_reg_set_val(regs, 29, *tmp); | |
| tmp += 1; | |
| } | |
| regs->usp = (unsigned long)tmp; | |
| instruction_pointer_set(regs, regs->lr); | |
| } | |
| void __kprobes | |
| simulate_bez32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| if (tmp == 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_bnez32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| csky_insn_reg_get_val(regs, tmp, &tmp); | |
| if (tmp != 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_bnezad32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| long val; | |
| csky_insn_reg_get_val(regs, tmp, (unsigned long *)&val); | |
| val -= 1; | |
| if (val > 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| csky_insn_reg_set_val(regs, tmp, (unsigned long)val); | |
| } | |
| void __kprobes | |
| simulate_bhsz32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| unsigned long val; | |
| csky_insn_reg_get_val(regs, tmp, &val); | |
| if ((long) val >= 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_bhz32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| unsigned long val; | |
| csky_insn_reg_get_val(regs, tmp, &val); | |
| if ((long) val > 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_blsz32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| unsigned long val; | |
| csky_insn_reg_get_val(regs, tmp, &val); | |
| if ((long) val <= 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_blz32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp = opcode & 0x1f; | |
| unsigned long val; | |
| csky_insn_reg_get_val(regs, tmp, &val); | |
| if ((long) val < 0) { | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((opcode & 0xffff0000) >> 15, 15)); | |
| } else | |
| instruction_pointer_set(regs, addr + 4); | |
| } | |
| void __kprobes | |
| simulate_bsr32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long tmp; | |
| tmp = (opcode & 0xffff) << 16; | |
| tmp |= (opcode & 0xffff0000) >> 16; | |
| instruction_pointer_set(regs, | |
| addr + sign_extend32((tmp & 0x3ffffff) << 1, 15)); | |
| regs->lr = addr + 4; | |
| } | |
| void __kprobes | |
| simulate_jmpi32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long val; | |
| unsigned long offset = ((opcode & 0xffff0000) >> 14); | |
| val = *(unsigned int *) | |
| ((instruction_pointer(regs) + offset) & 0xfffffffc); | |
| instruction_pointer_set(regs, val); | |
| } | |
| void __kprobes | |
| simulate_jsri32(u32 opcode, long addr, struct pt_regs *regs) | |
| { | |
| unsigned long val; | |
| unsigned long offset = ((opcode & 0xffff0000) >> 14); | |
| val = *(unsigned int *) | |
| ((instruction_pointer(regs) + offset) & 0xfffffffc); | |
| regs->lr = addr + 4; | |
| instruction_pointer_set(regs, val); | |
| } |