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
get_reg_val(struct pt_regs * fp,int reg)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
put_reg_val(struct pt_regs * fp,int reg,u32 val)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 */
handle_unaligned_c(struct pt_regs * fp,int cause)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
misaligned_calc_reg_offsets(void)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
misaligned_init(void)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