162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <linux/kernel.h> 562306a36Sopenharmony_ci#include <linux/uaccess.h> 662306a36Sopenharmony_ci#include <linux/ptrace.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_cistatic int align_kern_enable = 1; 962306a36Sopenharmony_cistatic int align_usr_enable = 1; 1062306a36Sopenharmony_cistatic int align_kern_count = 0; 1162306a36Sopenharmony_cistatic int align_usr_count = 0; 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx); 1662306a36Sopenharmony_ci} 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci if (rx == 15) 2162306a36Sopenharmony_ci regs->lr = val; 2262306a36Sopenharmony_ci else 2362306a36Sopenharmony_ci *((uint32_t *)&(regs->a0) - 2 + rx) = val; 2462306a36Sopenharmony_ci} 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* 2762306a36Sopenharmony_ci * Get byte-value from addr and set it to *valp. 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * Success: return 0 3062306a36Sopenharmony_ci * Failure: return 1 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistatic int ldb_asm(uint32_t addr, uint32_t *valp) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci uint32_t val; 3562306a36Sopenharmony_ci int err; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci asm volatile ( 3862306a36Sopenharmony_ci "movi %0, 0\n" 3962306a36Sopenharmony_ci "1:\n" 4062306a36Sopenharmony_ci "ldb %1, (%2)\n" 4162306a36Sopenharmony_ci "br 3f\n" 4262306a36Sopenharmony_ci "2:\n" 4362306a36Sopenharmony_ci "movi %0, 1\n" 4462306a36Sopenharmony_ci "br 3f\n" 4562306a36Sopenharmony_ci ".section __ex_table,\"a\"\n" 4662306a36Sopenharmony_ci ".align 2\n" 4762306a36Sopenharmony_ci ".long 1b, 2b\n" 4862306a36Sopenharmony_ci ".previous\n" 4962306a36Sopenharmony_ci "3:\n" 5062306a36Sopenharmony_ci : "=&r"(err), "=r"(val) 5162306a36Sopenharmony_ci : "r" (addr) 5262306a36Sopenharmony_ci ); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci *valp = val; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci return err; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* 6062306a36Sopenharmony_ci * Put byte-value to addr. 6162306a36Sopenharmony_ci * 6262306a36Sopenharmony_ci * Success: return 0 6362306a36Sopenharmony_ci * Failure: return 1 6462306a36Sopenharmony_ci */ 6562306a36Sopenharmony_cistatic int stb_asm(uint32_t addr, uint32_t val) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci int err; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci asm volatile ( 7062306a36Sopenharmony_ci "movi %0, 0\n" 7162306a36Sopenharmony_ci "1:\n" 7262306a36Sopenharmony_ci "stb %1, (%2)\n" 7362306a36Sopenharmony_ci "br 3f\n" 7462306a36Sopenharmony_ci "2:\n" 7562306a36Sopenharmony_ci "movi %0, 1\n" 7662306a36Sopenharmony_ci "br 3f\n" 7762306a36Sopenharmony_ci ".section __ex_table,\"a\"\n" 7862306a36Sopenharmony_ci ".align 2\n" 7962306a36Sopenharmony_ci ".long 1b, 2b\n" 8062306a36Sopenharmony_ci ".previous\n" 8162306a36Sopenharmony_ci "3:\n" 8262306a36Sopenharmony_ci : "=&r"(err) 8362306a36Sopenharmony_ci : "r"(val), "r" (addr) 8462306a36Sopenharmony_ci ); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return err; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* 9062306a36Sopenharmony_ci * Get half-word from [rx + imm] 9162306a36Sopenharmony_ci * 9262306a36Sopenharmony_ci * Success: return 0 9362306a36Sopenharmony_ci * Failure: return 1 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_cistatic int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci uint32_t byte0, byte1; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (ldb_asm(addr, &byte0)) 10062306a36Sopenharmony_ci return 1; 10162306a36Sopenharmony_ci addr += 1; 10262306a36Sopenharmony_ci if (ldb_asm(addr, &byte1)) 10362306a36Sopenharmony_ci return 1; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci byte0 |= byte1 << 8; 10662306a36Sopenharmony_ci put_ptreg(regs, rz, byte0); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* 11262306a36Sopenharmony_ci * Store half-word to [rx + imm] 11362306a36Sopenharmony_ci * 11462306a36Sopenharmony_ci * Success: return 0 11562306a36Sopenharmony_ci * Failure: return 1 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_cistatic int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci uint32_t byte0, byte1; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci byte0 = byte1 = get_ptreg(regs, rz); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci byte0 &= 0xff; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (stb_asm(addr, byte0)) 12662306a36Sopenharmony_ci return 1; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci addr += 1; 12962306a36Sopenharmony_ci byte1 = (byte1 >> 8) & 0xff; 13062306a36Sopenharmony_ci if (stb_asm(addr, byte1)) 13162306a36Sopenharmony_ci return 1; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci/* 13762306a36Sopenharmony_ci * Get word from [rx + imm] 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * Success: return 0 14062306a36Sopenharmony_ci * Failure: return 1 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_cistatic int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci uint32_t byte0, byte1, byte2, byte3; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (ldb_asm(addr, &byte0)) 14762306a36Sopenharmony_ci return 1; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci addr += 1; 15062306a36Sopenharmony_ci if (ldb_asm(addr, &byte1)) 15162306a36Sopenharmony_ci return 1; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci addr += 1; 15462306a36Sopenharmony_ci if (ldb_asm(addr, &byte2)) 15562306a36Sopenharmony_ci return 1; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci addr += 1; 15862306a36Sopenharmony_ci if (ldb_asm(addr, &byte3)) 15962306a36Sopenharmony_ci return 1; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci byte0 |= byte1 << 8; 16262306a36Sopenharmony_ci byte0 |= byte2 << 16; 16362306a36Sopenharmony_ci byte0 |= byte3 << 24; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci put_ptreg(regs, rz, byte0); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/* 17162306a36Sopenharmony_ci * Store word to [rx + imm] 17262306a36Sopenharmony_ci * 17362306a36Sopenharmony_ci * Success: return 0 17462306a36Sopenharmony_ci * Failure: return 1 17562306a36Sopenharmony_ci */ 17662306a36Sopenharmony_cistatic int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci uint32_t byte0, byte1, byte2, byte3; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci byte0 &= 0xff; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (stb_asm(addr, byte0)) 18562306a36Sopenharmony_ci return 1; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci addr += 1; 18862306a36Sopenharmony_ci byte1 = (byte1 >> 8) & 0xff; 18962306a36Sopenharmony_ci if (stb_asm(addr, byte1)) 19062306a36Sopenharmony_ci return 1; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci addr += 1; 19362306a36Sopenharmony_ci byte2 = (byte2 >> 16) & 0xff; 19462306a36Sopenharmony_ci if (stb_asm(addr, byte2)) 19562306a36Sopenharmony_ci return 1; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci addr += 1; 19862306a36Sopenharmony_ci byte3 = (byte3 >> 24) & 0xff; 19962306a36Sopenharmony_ci if (stb_asm(addr, byte3)) 20062306a36Sopenharmony_ci return 1; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return 0; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ciextern int fixup_exception(struct pt_regs *regs); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci#define OP_LDH 0xc000 20862306a36Sopenharmony_ci#define OP_STH 0xd000 20962306a36Sopenharmony_ci#define OP_LDW 0x8000 21062306a36Sopenharmony_ci#define OP_STW 0x9000 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_civoid csky_alignment(struct pt_regs *regs) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci int ret; 21562306a36Sopenharmony_ci uint16_t tmp; 21662306a36Sopenharmony_ci uint32_t opcode = 0; 21762306a36Sopenharmony_ci uint32_t rx = 0; 21862306a36Sopenharmony_ci uint32_t rz = 0; 21962306a36Sopenharmony_ci uint32_t imm = 0; 22062306a36Sopenharmony_ci uint32_t addr = 0; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (!user_mode(regs)) 22362306a36Sopenharmony_ci goto kernel_area; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (!align_usr_enable) { 22662306a36Sopenharmony_ci pr_err("%s user disabled.\n", __func__); 22762306a36Sopenharmony_ci goto bad_area; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci align_usr_count++; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci ret = get_user(tmp, (uint16_t *)instruction_pointer(regs)); 23362306a36Sopenharmony_ci if (ret) { 23462306a36Sopenharmony_ci pr_err("%s get_user failed.\n", __func__); 23562306a36Sopenharmony_ci goto bad_area; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci goto good_area; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cikernel_area: 24162306a36Sopenharmony_ci if (!align_kern_enable) { 24262306a36Sopenharmony_ci pr_err("%s kernel disabled.\n", __func__); 24362306a36Sopenharmony_ci goto bad_area; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci align_kern_count++; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci tmp = *(uint16_t *)instruction_pointer(regs); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cigood_area: 25162306a36Sopenharmony_ci opcode = (uint32_t)tmp; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci rx = opcode & 0xf; 25462306a36Sopenharmony_ci imm = (opcode >> 4) & 0xf; 25562306a36Sopenharmony_ci rz = (opcode >> 8) & 0xf; 25662306a36Sopenharmony_ci opcode &= 0xf000; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (rx == 0 || rx == 1 || rz == 0 || rz == 1) 25962306a36Sopenharmony_ci goto bad_area; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci switch (opcode) { 26262306a36Sopenharmony_ci case OP_LDH: 26362306a36Sopenharmony_ci addr = get_ptreg(regs, rx) + (imm << 1); 26462306a36Sopenharmony_ci ret = ldh_c(regs, rz, addr); 26562306a36Sopenharmony_ci break; 26662306a36Sopenharmony_ci case OP_LDW: 26762306a36Sopenharmony_ci addr = get_ptreg(regs, rx) + (imm << 2); 26862306a36Sopenharmony_ci ret = ldw_c(regs, rz, addr); 26962306a36Sopenharmony_ci break; 27062306a36Sopenharmony_ci case OP_STH: 27162306a36Sopenharmony_ci addr = get_ptreg(regs, rx) + (imm << 1); 27262306a36Sopenharmony_ci ret = sth_c(regs, rz, addr); 27362306a36Sopenharmony_ci break; 27462306a36Sopenharmony_ci case OP_STW: 27562306a36Sopenharmony_ci addr = get_ptreg(regs, rx) + (imm << 2); 27662306a36Sopenharmony_ci ret = stw_c(regs, rz, addr); 27762306a36Sopenharmony_ci break; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (ret) 28162306a36Sopenharmony_ci goto bad_area; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci regs->pc += 2; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cibad_area: 28862306a36Sopenharmony_ci if (!user_mode(regs)) { 28962306a36Sopenharmony_ci if (fixup_exception(regs)) 29062306a36Sopenharmony_ci return; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci bust_spinlocks(1); 29362306a36Sopenharmony_ci pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n", 29462306a36Sopenharmony_ci __func__, opcode, rz, rx, imm, addr); 29562306a36Sopenharmony_ci show_regs(regs); 29662306a36Sopenharmony_ci bust_spinlocks(0); 29762306a36Sopenharmony_ci make_task_dead(SIGKILL); 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr); 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cistatic struct ctl_table alignment_tbl[5] = { 30462306a36Sopenharmony_ci { 30562306a36Sopenharmony_ci .procname = "kernel_enable", 30662306a36Sopenharmony_ci .data = &align_kern_enable, 30762306a36Sopenharmony_ci .maxlen = sizeof(align_kern_enable), 30862306a36Sopenharmony_ci .mode = 0666, 30962306a36Sopenharmony_ci .proc_handler = &proc_dointvec 31062306a36Sopenharmony_ci }, 31162306a36Sopenharmony_ci { 31262306a36Sopenharmony_ci .procname = "user_enable", 31362306a36Sopenharmony_ci .data = &align_usr_enable, 31462306a36Sopenharmony_ci .maxlen = sizeof(align_usr_enable), 31562306a36Sopenharmony_ci .mode = 0666, 31662306a36Sopenharmony_ci .proc_handler = &proc_dointvec 31762306a36Sopenharmony_ci }, 31862306a36Sopenharmony_ci { 31962306a36Sopenharmony_ci .procname = "kernel_count", 32062306a36Sopenharmony_ci .data = &align_kern_count, 32162306a36Sopenharmony_ci .maxlen = sizeof(align_kern_count), 32262306a36Sopenharmony_ci .mode = 0666, 32362306a36Sopenharmony_ci .proc_handler = &proc_dointvec 32462306a36Sopenharmony_ci }, 32562306a36Sopenharmony_ci { 32662306a36Sopenharmony_ci .procname = "user_count", 32762306a36Sopenharmony_ci .data = &align_usr_count, 32862306a36Sopenharmony_ci .maxlen = sizeof(align_usr_count), 32962306a36Sopenharmony_ci .mode = 0666, 33062306a36Sopenharmony_ci .proc_handler = &proc_dointvec 33162306a36Sopenharmony_ci }, 33262306a36Sopenharmony_ci {} 33362306a36Sopenharmony_ci}; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int __init csky_alignment_init(void) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci register_sysctl_init("csky/csky_alignment", alignment_tbl); 33862306a36Sopenharmony_ci return 0; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ciarch_initcall(csky_alignment_init); 342