// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. #include <linux/kernel.h> #include <linux/uaccess.h> #include <linux/ptrace.h> static int align_kern_enable = 1; static int align_usr_enable = 1; static int align_kern_count = 0; static int align_usr_count = 0; static inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx) { return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx); } static inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val) { if (rx == 15) regs->lr = val; else *((uint32_t *)&(regs->a0) - 2 + rx) = val; } /* * Get byte-value from addr and set it to *valp. * * Success: return 0 * Failure: return 1 */ static int ldb_asm(uint32_t addr, uint32_t *valp) { uint32_t val; int err; asm volatile ( "movi %0, 0\n" "1:\n" "ldb %1, (%2)\n" "br 3f\n" "2:\n" "movi %0, 1\n" "br 3f\n" ".section __ex_table,\"a\"\n" ".align 2\n" ".long 1b, 2b\n" ".previous\n" "3:\n" : "=&r"(err), "=r"(val) : "r" (addr) ); *valp = val; return err; } /* * Put byte-value to addr. * * Success: return 0 * Failure: return 1 */ static int stb_asm(uint32_t addr, uint32_t val) { int err; asm volatile ( "movi %0, 0\n" "1:\n" "stb %1, (%2)\n" "br 3f\n" "2:\n" "movi %0, 1\n" "br 3f\n" ".section __ex_table,\"a\"\n" ".align 2\n" ".long 1b, 2b\n" ".previous\n" "3:\n" : "=&r"(err) : "r"(val), "r" (addr) ); return err; } /* * Get half-word from [rx + imm] * * Success: return 0 * Failure: return 1 */ static int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) { uint32_t byte0, byte1; if (ldb_asm(addr, &byte0)) return 1; addr += 1; if (ldb_asm(addr, &byte1)) return 1; byte0 |= byte1 << 8; put_ptreg(regs, rz, byte0); return 0; } /* * Store half-word to [rx + imm] * * Success: return 0 * Failure: return 1 */ static int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) { uint32_t byte0, byte1; byte0 = byte1 = get_ptreg(regs, rz); byte0 &= 0xff; if (stb_asm(addr, byte0)) return 1; addr += 1; byte1 = (byte1 >> 8) & 0xff; if (stb_asm(addr, byte1)) return 1; return 0; } /* * Get word from [rx + imm] * * Success: return 0 * Failure: return 1 */ static int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) { uint32_t byte0, byte1, byte2, byte3; if (ldb_asm(addr, &byte0)) return 1; addr += 1; if (ldb_asm(addr, &byte1)) return 1; addr += 1; if (ldb_asm(addr, &byte2)) return 1; addr += 1; if (ldb_asm(addr, &byte3)) return 1; byte0 |= byte1 << 8; byte0 |= byte2 << 16; byte0 |= byte3 << 24; put_ptreg(regs, rz, byte0); return 0; } /* * Store word to [rx + imm] * * Success: return 0 * Failure: return 1 */ static int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) { uint32_t byte0, byte1, byte2, byte3; byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz); byte0 &= 0xff; if (stb_asm(addr, byte0)) return 1; addr += 1; byte1 = (byte1 >> 8) & 0xff; if (stb_asm(addr, byte1)) return 1; addr += 1; byte2 = (byte2 >> 16) & 0xff; if (stb_asm(addr, byte2)) return 1; addr += 1; byte3 = (byte3 >> 24) & 0xff; if (stb_asm(addr, byte3)) return 1; return 0; } extern int fixup_exception(struct pt_regs *regs); #define OP_LDH 0xc000 #define OP_STH 0xd000 #define OP_LDW 0x8000 #define OP_STW 0x9000 void csky_alignment(struct pt_regs *regs) { int ret; uint16_t tmp; uint32_t opcode = 0; uint32_t rx = 0; uint32_t rz = 0; uint32_t imm = 0; uint32_t addr = 0; if (!user_mode(regs)) goto kernel_area; if (!align_usr_enable) { pr_err("%s user disabled.\n", __func__); goto bad_area; } align_usr_count++; ret = get_user(tmp, (uint16_t *)instruction_pointer(regs)); if (ret) { pr_err("%s get_user failed.\n", __func__); goto bad_area; } goto good_area; kernel_area: if (!align_kern_enable) { pr_err("%s kernel disabled.\n", __func__); goto bad_area; } align_kern_count++; tmp = *(uint16_t *)instruction_pointer(regs); good_area: opcode = (uint32_t)tmp; rx = opcode & 0xf; imm = (opcode >> 4) & 0xf; rz = (opcode >> 8) & 0xf; opcode &= 0xf000; if (rx == 0 || rx == 1 || rz == 0 || rz == 1) goto bad_area; switch (opcode) { case OP_LDH: addr = get_ptreg(regs, rx) + (imm << 1); ret = ldh_c(regs, rz, addr); break; case OP_LDW: addr = get_ptreg(regs, rx) + (imm << 2); ret = ldw_c(regs, rz, addr); break; case OP_STH: addr = get_ptreg(regs, rx) + (imm << 1); ret = sth_c(regs, rz, addr); break; case OP_STW: addr = get_ptreg(regs, rx) + (imm << 2); ret = stw_c(regs, rz, addr); break; } if (ret) goto bad_area; regs->pc += 2; return; bad_area: if (!user_mode(regs)) { if (fixup_exception(regs)) return; bust_spinlocks(1); pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n", __func__, opcode, rz, rx, imm, addr); show_regs(regs); bust_spinlocks(0); make_task_dead(SIGKILL); } force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr); } static struct ctl_table alignment_tbl[5] = { { .procname = "kernel_enable", .data = &align_kern_enable, .maxlen = sizeof(align_kern_enable), .mode = 0666, .proc_handler = &proc_dointvec }, { .procname = "user_enable", .data = &align_usr_enable, .maxlen = sizeof(align_usr_enable), .mode = 0666, .proc_handler = &proc_dointvec }, { .procname = "kernel_count", .data = &align_kern_count, .maxlen = sizeof(align_kern_count), .mode = 0666, .proc_handler = &proc_dointvec }, { .procname = "user_count", .data = &align_usr_count, .maxlen = sizeof(align_usr_count), .mode = 0666, .proc_handler = &proc_dointvec }, {} }; static int __init csky_alignment_init(void) { register_sysctl_init("csky/csky_alignment", alignment_tbl); return 0; } arch_initcall(csky_alignment_init);