xref: /linux/arch/nios2/kernel/misaligned.c (revision 5f60d5f6bbc12e782fac78110b0ee62698f3b576)
182ed08ddSLey Foon Tan /*
282ed08ddSLey Foon Tan  *  linux/arch/nios2/kernel/misaligned.c
382ed08ddSLey Foon Tan  *
482ed08ddSLey Foon Tan  *  basic emulation for mis-aligned accesses on the NIOS II cpu
582ed08ddSLey Foon Tan  *  modelled after the version for arm in arm/alignment.c
682ed08ddSLey Foon Tan  *
782ed08ddSLey Foon Tan  *  Brad Parker <brad@heeltoe.com>
882ed08ddSLey Foon Tan  *  Copyright (C) 2010 Ambient Corporation
982ed08ddSLey Foon Tan  *  Copyright (c) 2010 Altera Corporation, San Jose, California, USA.
1082ed08ddSLey Foon Tan  *  Copyright (c) 2010 Arrow Electronics, Inc.
1182ed08ddSLey Foon Tan  *
1282ed08ddSLey Foon Tan  * This file is subject to the terms and conditions of the GNU General
1382ed08ddSLey Foon Tan  * Public License.  See the file COPYING in the main directory of
1482ed08ddSLey Foon Tan  * this archive for more details.
1582ed08ddSLey Foon Tan  */
1682ed08ddSLey Foon Tan 
1782ed08ddSLey Foon Tan #include <linux/errno.h>
1882ed08ddSLey Foon Tan #include <linux/string.h>
1982ed08ddSLey Foon Tan #include <linux/proc_fs.h>
2082ed08ddSLey Foon Tan #include <linux/init.h>
2182ed08ddSLey Foon Tan #include <linux/sched.h>
2282ed08ddSLey Foon Tan #include <linux/uaccess.h>
2382ed08ddSLey Foon Tan #include <linux/seq_file.h>
2482ed08ddSLey Foon Tan 
2582ed08ddSLey Foon Tan #include <asm/traps.h>
26*5f60d5f6SAl Viro #include <linux/unaligned.h>
2782ed08ddSLey Foon Tan 
2882ed08ddSLey Foon Tan /* instructions we emulate */
2982ed08ddSLey Foon Tan #define INST_LDHU	0x0b
3082ed08ddSLey Foon Tan #define INST_STH	0x0d
3182ed08ddSLey Foon Tan #define INST_LDH	0x0f
3282ed08ddSLey Foon Tan #define INST_STW	0x15
3382ed08ddSLey Foon Tan #define INST_LDW	0x17
3482ed08ddSLey Foon Tan 
3582ed08ddSLey Foon Tan static unsigned int ma_usermode;
3682ed08ddSLey Foon Tan #define UM_WARN		0x01
3782ed08ddSLey Foon Tan #define UM_FIXUP	0x02
3882ed08ddSLey Foon Tan #define UM_SIGNAL	0x04
3982ed08ddSLey Foon Tan #define KM_WARN		0x08
4082ed08ddSLey Foon Tan 
4182ed08ddSLey Foon Tan /* see arch/nios2/include/asm/ptrace.h */
4282ed08ddSLey Foon Tan static u8 sys_stack_frame_reg_offset[] = {
4382ed08ddSLey Foon Tan 	/* struct pt_regs */
4482ed08ddSLey Foon Tan 	8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 0,
4582ed08ddSLey Foon Tan 	/* struct switch_stack */
4682ed08ddSLey Foon Tan 	16, 17, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0
4782ed08ddSLey Foon Tan };
4882ed08ddSLey Foon Tan 
4982ed08ddSLey Foon Tan static int reg_offsets[32];
5082ed08ddSLey Foon Tan 
5182ed08ddSLey Foon Tan static inline u32 get_reg_val(struct pt_regs *fp, int reg)
5282ed08ddSLey Foon Tan {
5382ed08ddSLey Foon Tan 	u8 *p = ((u8 *)fp) + reg_offsets[reg];
5482ed08ddSLey Foon Tan 	return *(u32 *)p;
5582ed08ddSLey Foon Tan }
5682ed08ddSLey Foon Tan 
5782ed08ddSLey Foon Tan static inline void put_reg_val(struct pt_regs *fp, int reg, u32 val)
5882ed08ddSLey Foon Tan {
5982ed08ddSLey Foon Tan 	u8 *p = ((u8 *)fp) + reg_offsets[reg];
6082ed08ddSLey Foon Tan 	*(u32 *)p = val;
6182ed08ddSLey Foon Tan }
6282ed08ddSLey Foon Tan 
6382ed08ddSLey Foon Tan /*
6482ed08ddSLey Foon Tan  * (mis)alignment handler
6582ed08ddSLey Foon Tan  */
6682ed08ddSLey Foon Tan asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause)
6782ed08ddSLey Foon Tan {
6882ed08ddSLey Foon Tan 	u32 isn, addr, val;
6982ed08ddSLey Foon Tan 	int in_kernel;
7082ed08ddSLey Foon Tan 	u8 a, b, d0, d1, d2, d3;
71db5a7e55SBernd Weiberg 	s16 imm16;
7282ed08ddSLey Foon Tan 	unsigned int fault;
7382ed08ddSLey Foon Tan 
7482ed08ddSLey Foon Tan 	/* back up one instruction */
7582ed08ddSLey Foon Tan 	fp->ea -= 4;
7682ed08ddSLey Foon Tan 
7782ed08ddSLey Foon Tan 	if (fixup_exception(fp)) {
7882ed08ddSLey Foon Tan 		return;
7982ed08ddSLey Foon Tan 	}
8082ed08ddSLey Foon Tan 
8182ed08ddSLey Foon Tan 	in_kernel = !user_mode(fp);
8282ed08ddSLey Foon Tan 
8382ed08ddSLey Foon Tan 	isn = *(unsigned long *)(fp->ea);
8482ed08ddSLey Foon Tan 
8582ed08ddSLey Foon Tan 	fault = 0;
8682ed08ddSLey Foon Tan 
8782ed08ddSLey Foon Tan 	/* do fixup if in kernel or mode turned on */
8882ed08ddSLey Foon Tan 	if (in_kernel || (ma_usermode & UM_FIXUP)) {
8982ed08ddSLey Foon Tan 		/* decompose instruction */
9082ed08ddSLey Foon Tan 		a = (isn >> 27) & 0x1f;
9182ed08ddSLey Foon Tan 		b = (isn >> 22) & 0x1f;
9282ed08ddSLey Foon Tan 		imm16 = (isn >> 6) & 0xffff;
9382ed08ddSLey Foon Tan 		addr = get_reg_val(fp, a) + imm16;
9482ed08ddSLey Foon Tan 
9582ed08ddSLey Foon Tan 		/* do fixup to saved registers */
9682ed08ddSLey Foon Tan 		switch (isn & 0x3f) {
9782ed08ddSLey Foon Tan 		case INST_LDHU:
9882ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
9982ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
10082ed08ddSLey Foon Tan 			val = (d1 << 8) | d0;
10182ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
10282ed08ddSLey Foon Tan 			break;
10382ed08ddSLey Foon Tan 		case INST_STH:
10482ed08ddSLey Foon Tan 			val = get_reg_val(fp, b);
10582ed08ddSLey Foon Tan 			d1 = val >> 8;
10682ed08ddSLey Foon Tan 			d0 = val >> 0;
10782ed08ddSLey Foon Tan 			if (in_kernel) {
10882ed08ddSLey Foon Tan 				*(u8 *)(addr+0) = d0;
10982ed08ddSLey Foon Tan 				*(u8 *)(addr+1) = d1;
11082ed08ddSLey Foon Tan 			} else {
11182ed08ddSLey Foon Tan 				fault |= __put_user(d0, (u8 *)(addr+0));
11282ed08ddSLey Foon Tan 				fault |= __put_user(d1, (u8 *)(addr+1));
11382ed08ddSLey Foon Tan 			}
11482ed08ddSLey Foon Tan 			break;
11582ed08ddSLey Foon Tan 		case INST_LDH:
11682ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
11782ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
11882ed08ddSLey Foon Tan 			val = (short)((d1 << 8) | d0);
11982ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
12082ed08ddSLey Foon Tan 			break;
12182ed08ddSLey Foon Tan 		case INST_STW:
12282ed08ddSLey Foon Tan 			val = get_reg_val(fp, b);
12382ed08ddSLey Foon Tan 			d3 = val >> 24;
12482ed08ddSLey Foon Tan 			d2 = val >> 16;
12582ed08ddSLey Foon Tan 			d1 = val >> 8;
12682ed08ddSLey Foon Tan 			d0 = val >> 0;
12782ed08ddSLey Foon Tan 			if (in_kernel) {
12882ed08ddSLey Foon Tan 				*(u8 *)(addr+0) = d0;
12982ed08ddSLey Foon Tan 				*(u8 *)(addr+1) = d1;
13082ed08ddSLey Foon Tan 				*(u8 *)(addr+2) = d2;
13182ed08ddSLey Foon Tan 				*(u8 *)(addr+3) = d3;
13282ed08ddSLey Foon Tan 			} else {
13382ed08ddSLey Foon Tan 				fault |= __put_user(d0, (u8 *)(addr+0));
13482ed08ddSLey Foon Tan 				fault |= __put_user(d1, (u8 *)(addr+1));
13582ed08ddSLey Foon Tan 				fault |= __put_user(d2, (u8 *)(addr+2));
13682ed08ddSLey Foon Tan 				fault |= __put_user(d3, (u8 *)(addr+3));
13782ed08ddSLey Foon Tan 			}
13882ed08ddSLey Foon Tan 			break;
13982ed08ddSLey Foon Tan 		case INST_LDW:
14082ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
14182ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
14282ed08ddSLey Foon Tan 			fault |= __get_user(d2, (u8 *)(addr+2));
14382ed08ddSLey Foon Tan 			fault |= __get_user(d3, (u8 *)(addr+3));
14482ed08ddSLey Foon Tan 			val = (d3 << 24) | (d2 << 16) | (d1 << 8) | d0;
14582ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
14682ed08ddSLey Foon Tan 			break;
14782ed08ddSLey Foon Tan 		}
14882ed08ddSLey Foon Tan 	}
14982ed08ddSLey Foon Tan 
15082ed08ddSLey Foon Tan 	addr = RDCTL(CTL_BADADDR);
15182ed08ddSLey Foon Tan 	cause >>= 2;
15282ed08ddSLey Foon Tan 
15382ed08ddSLey Foon Tan 	if (fault) {
15482ed08ddSLey Foon Tan 		if (in_kernel) {
15582ed08ddSLey Foon Tan 			pr_err("fault during kernel misaligned fixup @ %#lx; addr 0x%08x; isn=0x%08x\n",
15682ed08ddSLey Foon Tan 				fp->ea, (unsigned int)addr,
15782ed08ddSLey Foon Tan 				(unsigned int)isn);
15882ed08ddSLey Foon Tan 		} else {
15982ed08ddSLey Foon Tan 			pr_err("fault during user misaligned fixup @ %#lx; isn=%08x addr=0x%08x sp=0x%08lx pid=%d\n",
16082ed08ddSLey Foon Tan 				fp->ea,
16182ed08ddSLey Foon Tan 				(unsigned int)isn, addr, fp->sp,
16282ed08ddSLey Foon Tan 				current->pid);
16382ed08ddSLey Foon Tan 
16482ed08ddSLey Foon Tan 			_exception(SIGSEGV, fp, SEGV_MAPERR, fp->ea);
16582ed08ddSLey Foon Tan 			return;
16682ed08ddSLey Foon Tan 		}
16782ed08ddSLey Foon Tan 	}
16882ed08ddSLey Foon Tan 
16982ed08ddSLey Foon Tan 	/*
17082ed08ddSLey Foon Tan 	 * kernel mode -
17182ed08ddSLey Foon Tan 	 *  note exception and skip bad instruction (return)
17282ed08ddSLey Foon Tan 	 */
17382ed08ddSLey Foon Tan 	if (in_kernel) {
17482ed08ddSLey Foon Tan 		fp->ea += 4;
17582ed08ddSLey Foon Tan 
17682ed08ddSLey Foon Tan 		if (ma_usermode & KM_WARN) {
17782ed08ddSLey Foon Tan 			pr_err("kernel unaligned access @ %#lx; BADADDR 0x%08x; cause=%d, isn=0x%08x\n",
17882ed08ddSLey Foon Tan 				fp->ea,
17982ed08ddSLey Foon Tan 				(unsigned int)addr, cause,
18082ed08ddSLey Foon Tan 				(unsigned int)isn);
18182ed08ddSLey Foon Tan 			/* show_regs(fp); */
18282ed08ddSLey Foon Tan 		}
18382ed08ddSLey Foon Tan 
18482ed08ddSLey Foon Tan 		return;
18582ed08ddSLey Foon Tan 	}
18682ed08ddSLey Foon Tan 
18782ed08ddSLey Foon Tan 	/*
18882ed08ddSLey Foon Tan 	 * user mode -
18982ed08ddSLey Foon Tan 	 *  possibly warn,
19082ed08ddSLey Foon Tan 	 *  possibly send SIGBUS signal to process
19182ed08ddSLey Foon Tan 	 */
19282ed08ddSLey Foon Tan 	if (ma_usermode & UM_WARN) {
19382ed08ddSLey Foon Tan 		pr_err("user unaligned access @ %#lx; isn=0x%08lx ea=0x%08lx ra=0x%08lx sp=0x%08lx\n",
19482ed08ddSLey Foon Tan 			(unsigned long)addr, (unsigned long)isn,
19582ed08ddSLey Foon Tan 			fp->ea, fp->ra, fp->sp);
19682ed08ddSLey Foon Tan 	}
19782ed08ddSLey Foon Tan 
19882ed08ddSLey Foon Tan 	if (ma_usermode & UM_SIGNAL)
19982ed08ddSLey Foon Tan 		_exception(SIGBUS, fp, BUS_ADRALN, fp->ea);
20082ed08ddSLey Foon Tan 	else
20182ed08ddSLey Foon Tan 		fp->ea += 4;	/* else advance */
20282ed08ddSLey Foon Tan }
20382ed08ddSLey Foon Tan 
20482ed08ddSLey Foon Tan static void __init misaligned_calc_reg_offsets(void)
20582ed08ddSLey Foon Tan {
20682ed08ddSLey Foon Tan 	int i, r, offset;
20782ed08ddSLey Foon Tan 
20882ed08ddSLey Foon Tan 	/* pre-calc offsets of registers on sys call stack frame */
20982ed08ddSLey Foon Tan 	offset = 0;
21082ed08ddSLey Foon Tan 
21182ed08ddSLey Foon Tan 	/* struct pt_regs */
21282ed08ddSLey Foon Tan 	for (i = 0; i < 16; i++) {
21382ed08ddSLey Foon Tan 		r = sys_stack_frame_reg_offset[i];
21482ed08ddSLey Foon Tan 		reg_offsets[r] = offset;
21582ed08ddSLey Foon Tan 		offset += 4;
21682ed08ddSLey Foon Tan 	}
21782ed08ddSLey Foon Tan 
21882ed08ddSLey Foon Tan 	/* struct switch_stack */
21982ed08ddSLey Foon Tan 	offset = -sizeof(struct switch_stack);
22082ed08ddSLey Foon Tan 	for (i = 16; i < 32; i++) {
22182ed08ddSLey Foon Tan 		r = sys_stack_frame_reg_offset[i];
22282ed08ddSLey Foon Tan 		reg_offsets[r] = offset;
22382ed08ddSLey Foon Tan 		offset += 4;
22482ed08ddSLey Foon Tan 	}
22582ed08ddSLey Foon Tan }
22682ed08ddSLey Foon Tan 
22782ed08ddSLey Foon Tan 
22882ed08ddSLey Foon Tan static int __init misaligned_init(void)
22982ed08ddSLey Foon Tan {
23082ed08ddSLey Foon Tan 	/* default mode - silent fix */
23182ed08ddSLey Foon Tan 	ma_usermode = UM_FIXUP | KM_WARN;
23282ed08ddSLey Foon Tan 
23382ed08ddSLey Foon Tan 	misaligned_calc_reg_offsets();
23482ed08ddSLey Foon Tan 
23582ed08ddSLey Foon Tan 	return 0;
23682ed08ddSLey Foon Tan }
23782ed08ddSLey Foon Tan 
23882ed08ddSLey Foon Tan fs_initcall(misaligned_init);
239