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