18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci#include <linux/kernel.h>
58c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
68c2ecf20Sopenharmony_ci#include <linux/ptrace.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_cistatic int align_kern_enable = 1;
98c2ecf20Sopenharmony_cistatic int align_usr_enable = 1;
108c2ecf20Sopenharmony_cistatic int align_kern_count = 0;
118c2ecf20Sopenharmony_cistatic int align_usr_count = 0;
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistatic inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx)
148c2ecf20Sopenharmony_ci{
158c2ecf20Sopenharmony_ci	return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx);
168c2ecf20Sopenharmony_ci}
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	if (rx == 15)
218c2ecf20Sopenharmony_ci		regs->lr = val;
228c2ecf20Sopenharmony_ci	else
238c2ecf20Sopenharmony_ci		*((uint32_t *)&(regs->a0) - 2 + rx) = val;
248c2ecf20Sopenharmony_ci}
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/*
278c2ecf20Sopenharmony_ci * Get byte-value from addr and set it to *valp.
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci * Success: return 0
308c2ecf20Sopenharmony_ci * Failure: return 1
318c2ecf20Sopenharmony_ci */
328c2ecf20Sopenharmony_cistatic int ldb_asm(uint32_t addr, uint32_t *valp)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	uint32_t val;
358c2ecf20Sopenharmony_ci	int err;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	asm volatile (
388c2ecf20Sopenharmony_ci		"movi	%0, 0\n"
398c2ecf20Sopenharmony_ci		"1:\n"
408c2ecf20Sopenharmony_ci		"ldb	%1, (%2)\n"
418c2ecf20Sopenharmony_ci		"br	3f\n"
428c2ecf20Sopenharmony_ci		"2:\n"
438c2ecf20Sopenharmony_ci		"movi	%0, 1\n"
448c2ecf20Sopenharmony_ci		"br	3f\n"
458c2ecf20Sopenharmony_ci		".section __ex_table,\"a\"\n"
468c2ecf20Sopenharmony_ci		".align 2\n"
478c2ecf20Sopenharmony_ci		".long	1b, 2b\n"
488c2ecf20Sopenharmony_ci		".previous\n"
498c2ecf20Sopenharmony_ci		"3:\n"
508c2ecf20Sopenharmony_ci		: "=&r"(err), "=r"(val)
518c2ecf20Sopenharmony_ci		: "r" (addr)
528c2ecf20Sopenharmony_ci	);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	*valp = val;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	return err;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci/*
608c2ecf20Sopenharmony_ci * Put byte-value to addr.
618c2ecf20Sopenharmony_ci *
628c2ecf20Sopenharmony_ci * Success: return 0
638c2ecf20Sopenharmony_ci * Failure: return 1
648c2ecf20Sopenharmony_ci */
658c2ecf20Sopenharmony_cistatic int stb_asm(uint32_t addr, uint32_t val)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	int err;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	asm volatile (
708c2ecf20Sopenharmony_ci		"movi	%0, 0\n"
718c2ecf20Sopenharmony_ci		"1:\n"
728c2ecf20Sopenharmony_ci		"stb	%1, (%2)\n"
738c2ecf20Sopenharmony_ci		"br	3f\n"
748c2ecf20Sopenharmony_ci		"2:\n"
758c2ecf20Sopenharmony_ci		"movi	%0, 1\n"
768c2ecf20Sopenharmony_ci		"br	3f\n"
778c2ecf20Sopenharmony_ci		".section __ex_table,\"a\"\n"
788c2ecf20Sopenharmony_ci		".align 2\n"
798c2ecf20Sopenharmony_ci		".long	1b, 2b\n"
808c2ecf20Sopenharmony_ci		".previous\n"
818c2ecf20Sopenharmony_ci		"3:\n"
828c2ecf20Sopenharmony_ci		: "=&r"(err)
838c2ecf20Sopenharmony_ci		: "r"(val), "r" (addr)
848c2ecf20Sopenharmony_ci	);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return err;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/*
908c2ecf20Sopenharmony_ci * Get half-word from [rx + imm]
918c2ecf20Sopenharmony_ci *
928c2ecf20Sopenharmony_ci * Success: return 0
938c2ecf20Sopenharmony_ci * Failure: return 1
948c2ecf20Sopenharmony_ci */
958c2ecf20Sopenharmony_cistatic int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	uint32_t byte0, byte1;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte0))
1008c2ecf20Sopenharmony_ci		return 1;
1018c2ecf20Sopenharmony_ci	addr += 1;
1028c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte1))
1038c2ecf20Sopenharmony_ci		return 1;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	byte0 |= byte1 << 8;
1068c2ecf20Sopenharmony_ci	put_ptreg(regs, rz, byte0);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return 0;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci/*
1128c2ecf20Sopenharmony_ci * Store half-word to [rx + imm]
1138c2ecf20Sopenharmony_ci *
1148c2ecf20Sopenharmony_ci * Success: return 0
1158c2ecf20Sopenharmony_ci * Failure: return 1
1168c2ecf20Sopenharmony_ci */
1178c2ecf20Sopenharmony_cistatic int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	uint32_t byte0, byte1;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	byte0 = byte1 = get_ptreg(regs, rz);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	byte0 &= 0xff;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte0))
1268c2ecf20Sopenharmony_ci		return 1;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	addr += 1;
1298c2ecf20Sopenharmony_ci	byte1 = (byte1 >> 8) & 0xff;
1308c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte1))
1318c2ecf20Sopenharmony_ci		return 1;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci/*
1378c2ecf20Sopenharmony_ci * Get word from [rx + imm]
1388c2ecf20Sopenharmony_ci *
1398c2ecf20Sopenharmony_ci * Success: return 0
1408c2ecf20Sopenharmony_ci * Failure: return 1
1418c2ecf20Sopenharmony_ci */
1428c2ecf20Sopenharmony_cistatic int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	uint32_t byte0, byte1, byte2, byte3;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte0))
1478c2ecf20Sopenharmony_ci		return 1;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	addr += 1;
1508c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte1))
1518c2ecf20Sopenharmony_ci		return 1;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	addr += 1;
1548c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte2))
1558c2ecf20Sopenharmony_ci		return 1;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	addr += 1;
1588c2ecf20Sopenharmony_ci	if (ldb_asm(addr, &byte3))
1598c2ecf20Sopenharmony_ci		return 1;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	byte0 |= byte1 << 8;
1628c2ecf20Sopenharmony_ci	byte0 |= byte2 << 16;
1638c2ecf20Sopenharmony_ci	byte0 |= byte3 << 24;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	put_ptreg(regs, rz, byte0);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return 0;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci/*
1718c2ecf20Sopenharmony_ci * Store word to [rx + imm]
1728c2ecf20Sopenharmony_ci *
1738c2ecf20Sopenharmony_ci * Success: return 0
1748c2ecf20Sopenharmony_ci * Failure: return 1
1758c2ecf20Sopenharmony_ci */
1768c2ecf20Sopenharmony_cistatic int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	uint32_t byte0, byte1, byte2, byte3;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	byte0 &= 0xff;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte0))
1858c2ecf20Sopenharmony_ci		return 1;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	addr += 1;
1888c2ecf20Sopenharmony_ci	byte1 = (byte1 >> 8) & 0xff;
1898c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte1))
1908c2ecf20Sopenharmony_ci		return 1;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	addr += 1;
1938c2ecf20Sopenharmony_ci	byte2 = (byte2 >> 16) & 0xff;
1948c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte2))
1958c2ecf20Sopenharmony_ci		return 1;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	addr += 1;
1988c2ecf20Sopenharmony_ci	byte3 = (byte3 >> 24) & 0xff;
1998c2ecf20Sopenharmony_ci	if (stb_asm(addr, byte3))
2008c2ecf20Sopenharmony_ci		return 1;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return 0;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ciextern int fixup_exception(struct pt_regs *regs);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci#define OP_LDH 0xc000
2088c2ecf20Sopenharmony_ci#define OP_STH 0xd000
2098c2ecf20Sopenharmony_ci#define OP_LDW 0x8000
2108c2ecf20Sopenharmony_ci#define OP_STW 0x9000
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_civoid csky_alignment(struct pt_regs *regs)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	int ret;
2158c2ecf20Sopenharmony_ci	uint16_t tmp;
2168c2ecf20Sopenharmony_ci	uint32_t opcode = 0;
2178c2ecf20Sopenharmony_ci	uint32_t rx     = 0;
2188c2ecf20Sopenharmony_ci	uint32_t rz     = 0;
2198c2ecf20Sopenharmony_ci	uint32_t imm    = 0;
2208c2ecf20Sopenharmony_ci	uint32_t addr   = 0;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	if (!user_mode(regs))
2238c2ecf20Sopenharmony_ci		goto kernel_area;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	if (!align_usr_enable) {
2268c2ecf20Sopenharmony_ci		pr_err("%s user disabled.\n", __func__);
2278c2ecf20Sopenharmony_ci		goto bad_area;
2288c2ecf20Sopenharmony_ci	}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	align_usr_count++;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	ret = get_user(tmp, (uint16_t *)instruction_pointer(regs));
2338c2ecf20Sopenharmony_ci	if (ret) {
2348c2ecf20Sopenharmony_ci		pr_err("%s get_user failed.\n", __func__);
2358c2ecf20Sopenharmony_ci		goto bad_area;
2368c2ecf20Sopenharmony_ci	}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	goto good_area;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_cikernel_area:
2418c2ecf20Sopenharmony_ci	if (!align_kern_enable) {
2428c2ecf20Sopenharmony_ci		pr_err("%s kernel disabled.\n", __func__);
2438c2ecf20Sopenharmony_ci		goto bad_area;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	align_kern_count++;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	tmp = *(uint16_t *)instruction_pointer(regs);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_cigood_area:
2518c2ecf20Sopenharmony_ci	opcode = (uint32_t)tmp;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	rx  = opcode & 0xf;
2548c2ecf20Sopenharmony_ci	imm = (opcode >> 4) & 0xf;
2558c2ecf20Sopenharmony_ci	rz  = (opcode >> 8) & 0xf;
2568c2ecf20Sopenharmony_ci	opcode &= 0xf000;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	if (rx == 0 || rx == 1 || rz == 0 || rz == 1)
2598c2ecf20Sopenharmony_ci		goto bad_area;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	switch (opcode) {
2628c2ecf20Sopenharmony_ci	case OP_LDH:
2638c2ecf20Sopenharmony_ci		addr = get_ptreg(regs, rx) + (imm << 1);
2648c2ecf20Sopenharmony_ci		ret = ldh_c(regs, rz, addr);
2658c2ecf20Sopenharmony_ci		break;
2668c2ecf20Sopenharmony_ci	case OP_LDW:
2678c2ecf20Sopenharmony_ci		addr = get_ptreg(regs, rx) + (imm << 2);
2688c2ecf20Sopenharmony_ci		ret = ldw_c(regs, rz, addr);
2698c2ecf20Sopenharmony_ci		break;
2708c2ecf20Sopenharmony_ci	case OP_STH:
2718c2ecf20Sopenharmony_ci		addr = get_ptreg(regs, rx) + (imm << 1);
2728c2ecf20Sopenharmony_ci		ret = sth_c(regs, rz, addr);
2738c2ecf20Sopenharmony_ci		break;
2748c2ecf20Sopenharmony_ci	case OP_STW:
2758c2ecf20Sopenharmony_ci		addr = get_ptreg(regs, rx) + (imm << 2);
2768c2ecf20Sopenharmony_ci		ret = stw_c(regs, rz, addr);
2778c2ecf20Sopenharmony_ci		break;
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	if (ret)
2818c2ecf20Sopenharmony_ci		goto bad_area;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	regs->pc += 2;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cibad_area:
2888c2ecf20Sopenharmony_ci	if (!user_mode(regs)) {
2898c2ecf20Sopenharmony_ci		if (fixup_exception(regs))
2908c2ecf20Sopenharmony_ci			return;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci		bust_spinlocks(1);
2938c2ecf20Sopenharmony_ci		pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n",
2948c2ecf20Sopenharmony_ci				__func__, opcode, rz, rx, imm, addr);
2958c2ecf20Sopenharmony_ci		show_regs(regs);
2968c2ecf20Sopenharmony_ci		bust_spinlocks(0);
2978c2ecf20Sopenharmony_ci		make_task_dead(SIGKILL);
2988c2ecf20Sopenharmony_ci	}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr);
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic struct ctl_table alignment_tbl[5] = {
3048c2ecf20Sopenharmony_ci	{
3058c2ecf20Sopenharmony_ci		.procname = "kernel_enable",
3068c2ecf20Sopenharmony_ci		.data = &align_kern_enable,
3078c2ecf20Sopenharmony_ci		.maxlen = sizeof(align_kern_enable),
3088c2ecf20Sopenharmony_ci		.mode = 0666,
3098c2ecf20Sopenharmony_ci		.proc_handler = &proc_dointvec
3108c2ecf20Sopenharmony_ci	},
3118c2ecf20Sopenharmony_ci	{
3128c2ecf20Sopenharmony_ci		.procname = "user_enable",
3138c2ecf20Sopenharmony_ci		.data = &align_usr_enable,
3148c2ecf20Sopenharmony_ci		.maxlen = sizeof(align_usr_enable),
3158c2ecf20Sopenharmony_ci		.mode = 0666,
3168c2ecf20Sopenharmony_ci		.proc_handler = &proc_dointvec
3178c2ecf20Sopenharmony_ci	},
3188c2ecf20Sopenharmony_ci	{
3198c2ecf20Sopenharmony_ci		.procname = "kernel_count",
3208c2ecf20Sopenharmony_ci		.data = &align_kern_count,
3218c2ecf20Sopenharmony_ci		.maxlen = sizeof(align_kern_count),
3228c2ecf20Sopenharmony_ci		.mode = 0666,
3238c2ecf20Sopenharmony_ci		.proc_handler = &proc_dointvec
3248c2ecf20Sopenharmony_ci	},
3258c2ecf20Sopenharmony_ci	{
3268c2ecf20Sopenharmony_ci		.procname = "user_count",
3278c2ecf20Sopenharmony_ci		.data = &align_usr_count,
3288c2ecf20Sopenharmony_ci		.maxlen = sizeof(align_usr_count),
3298c2ecf20Sopenharmony_ci		.mode = 0666,
3308c2ecf20Sopenharmony_ci		.proc_handler = &proc_dointvec
3318c2ecf20Sopenharmony_ci	},
3328c2ecf20Sopenharmony_ci	{}
3338c2ecf20Sopenharmony_ci};
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic struct ctl_table sysctl_table[2] = {
3368c2ecf20Sopenharmony_ci	{
3378c2ecf20Sopenharmony_ci	 .procname = "csky_alignment",
3388c2ecf20Sopenharmony_ci	 .mode = 0555,
3398c2ecf20Sopenharmony_ci	 .child = alignment_tbl},
3408c2ecf20Sopenharmony_ci	{}
3418c2ecf20Sopenharmony_ci};
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_cistatic struct ctl_path sysctl_path[2] = {
3448c2ecf20Sopenharmony_ci	{.procname = "csky"},
3458c2ecf20Sopenharmony_ci	{}
3468c2ecf20Sopenharmony_ci};
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_cistatic int __init csky_alignment_init(void)
3498c2ecf20Sopenharmony_ci{
3508c2ecf20Sopenharmony_ci	register_sysctl_paths(sysctl_path, sysctl_table);
3518c2ecf20Sopenharmony_ci	return 0;
3528c2ecf20Sopenharmony_ci}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ciarch_initcall(csky_alignment_init);
355