1*82ed08ddSLey Foon Tan /* 2*82ed08ddSLey Foon Tan * linux/arch/nios2/kernel/misaligned.c 3*82ed08ddSLey Foon Tan * 4*82ed08ddSLey Foon Tan * basic emulation for mis-aligned accesses on the NIOS II cpu 5*82ed08ddSLey Foon Tan * modelled after the version for arm in arm/alignment.c 6*82ed08ddSLey Foon Tan * 7*82ed08ddSLey Foon Tan * Brad Parker <brad@heeltoe.com> 8*82ed08ddSLey Foon Tan * Copyright (C) 2010 Ambient Corporation 9*82ed08ddSLey Foon Tan * Copyright (c) 2010 Altera Corporation, San Jose, California, USA. 10*82ed08ddSLey Foon Tan * Copyright (c) 2010 Arrow Electronics, Inc. 11*82ed08ddSLey Foon Tan * 12*82ed08ddSLey Foon Tan * This file is subject to the terms and conditions of the GNU General 13*82ed08ddSLey Foon Tan * Public License. See the file COPYING in the main directory of 14*82ed08ddSLey Foon Tan * this archive for more details. 15*82ed08ddSLey Foon Tan */ 16*82ed08ddSLey Foon Tan 17*82ed08ddSLey Foon Tan #include <linux/errno.h> 18*82ed08ddSLey Foon Tan #include <linux/string.h> 19*82ed08ddSLey Foon Tan #include <linux/proc_fs.h> 20*82ed08ddSLey Foon Tan #include <linux/init.h> 21*82ed08ddSLey Foon Tan #include <linux/sched.h> 22*82ed08ddSLey Foon Tan #include <linux/uaccess.h> 23*82ed08ddSLey Foon Tan #include <linux/seq_file.h> 24*82ed08ddSLey Foon Tan 25*82ed08ddSLey Foon Tan #include <asm/traps.h> 26*82ed08ddSLey Foon Tan #include <asm/unaligned.h> 27*82ed08ddSLey Foon Tan 28*82ed08ddSLey Foon Tan /* instructions we emulate */ 29*82ed08ddSLey Foon Tan #define INST_LDHU 0x0b 30*82ed08ddSLey Foon Tan #define INST_STH 0x0d 31*82ed08ddSLey Foon Tan #define INST_LDH 0x0f 32*82ed08ddSLey Foon Tan #define INST_STW 0x15 33*82ed08ddSLey Foon Tan #define INST_LDW 0x17 34*82ed08ddSLey Foon Tan 35*82ed08ddSLey Foon Tan static unsigned long ma_user, ma_kern, ma_skipped, ma_half, ma_word; 36*82ed08ddSLey Foon Tan 37*82ed08ddSLey Foon Tan static unsigned int ma_usermode; 38*82ed08ddSLey Foon Tan #define UM_WARN 0x01 39*82ed08ddSLey Foon Tan #define UM_FIXUP 0x02 40*82ed08ddSLey Foon Tan #define UM_SIGNAL 0x04 41*82ed08ddSLey Foon Tan #define KM_WARN 0x08 42*82ed08ddSLey Foon Tan 43*82ed08ddSLey Foon Tan /* see arch/nios2/include/asm/ptrace.h */ 44*82ed08ddSLey Foon Tan static u8 sys_stack_frame_reg_offset[] = { 45*82ed08ddSLey Foon Tan /* struct pt_regs */ 46*82ed08ddSLey Foon Tan 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 0, 47*82ed08ddSLey Foon Tan /* struct switch_stack */ 48*82ed08ddSLey Foon Tan 16, 17, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0 49*82ed08ddSLey Foon Tan }; 50*82ed08ddSLey Foon Tan 51*82ed08ddSLey Foon Tan static int reg_offsets[32]; 52*82ed08ddSLey Foon Tan 53*82ed08ddSLey Foon Tan static inline u32 get_reg_val(struct pt_regs *fp, int reg) 54*82ed08ddSLey Foon Tan { 55*82ed08ddSLey Foon Tan u8 *p = ((u8 *)fp) + reg_offsets[reg]; 56*82ed08ddSLey Foon Tan 57*82ed08ddSLey Foon Tan return *(u32 *)p; 58*82ed08ddSLey Foon Tan } 59*82ed08ddSLey Foon Tan 60*82ed08ddSLey Foon Tan static inline void put_reg_val(struct pt_regs *fp, int reg, u32 val) 61*82ed08ddSLey Foon Tan { 62*82ed08ddSLey Foon Tan u8 *p = ((u8 *)fp) + reg_offsets[reg]; 63*82ed08ddSLey Foon Tan *(u32 *)p = val; 64*82ed08ddSLey Foon Tan } 65*82ed08ddSLey Foon Tan 66*82ed08ddSLey Foon Tan /* 67*82ed08ddSLey Foon Tan * (mis)alignment handler 68*82ed08ddSLey Foon Tan */ 69*82ed08ddSLey Foon Tan asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause) 70*82ed08ddSLey Foon Tan { 71*82ed08ddSLey Foon Tan u32 isn, addr, val; 72*82ed08ddSLey Foon Tan int in_kernel; 73*82ed08ddSLey Foon Tan u8 a, b, d0, d1, d2, d3; 74*82ed08ddSLey Foon Tan u16 imm16; 75*82ed08ddSLey Foon Tan unsigned int fault; 76*82ed08ddSLey Foon Tan 77*82ed08ddSLey Foon Tan /* back up one instruction */ 78*82ed08ddSLey Foon Tan fp->ea -= 4; 79*82ed08ddSLey Foon Tan 80*82ed08ddSLey Foon Tan if (fixup_exception(fp)) { 81*82ed08ddSLey Foon Tan ma_skipped++; 82*82ed08ddSLey Foon Tan return; 83*82ed08ddSLey Foon Tan } 84*82ed08ddSLey Foon Tan 85*82ed08ddSLey Foon Tan in_kernel = !user_mode(fp); 86*82ed08ddSLey Foon Tan 87*82ed08ddSLey Foon Tan isn = *(unsigned long *)(fp->ea); 88*82ed08ddSLey Foon Tan 89*82ed08ddSLey Foon Tan fault = 0; 90*82ed08ddSLey Foon Tan 91*82ed08ddSLey Foon Tan /* do fixup if in kernel or mode turned on */ 92*82ed08ddSLey Foon Tan if (in_kernel || (ma_usermode & UM_FIXUP)) { 93*82ed08ddSLey Foon Tan /* decompose instruction */ 94*82ed08ddSLey Foon Tan a = (isn >> 27) & 0x1f; 95*82ed08ddSLey Foon Tan b = (isn >> 22) & 0x1f; 96*82ed08ddSLey Foon Tan imm16 = (isn >> 6) & 0xffff; 97*82ed08ddSLey Foon Tan addr = get_reg_val(fp, a) + imm16; 98*82ed08ddSLey Foon Tan 99*82ed08ddSLey Foon Tan /* do fixup to saved registers */ 100*82ed08ddSLey Foon Tan switch (isn & 0x3f) { 101*82ed08ddSLey Foon Tan case INST_LDHU: 102*82ed08ddSLey Foon Tan fault |= __get_user(d0, (u8 *)(addr+0)); 103*82ed08ddSLey Foon Tan fault |= __get_user(d1, (u8 *)(addr+1)); 104*82ed08ddSLey Foon Tan val = (d1 << 8) | d0; 105*82ed08ddSLey Foon Tan put_reg_val(fp, b, val); 106*82ed08ddSLey Foon Tan ma_half++; 107*82ed08ddSLey Foon Tan break; 108*82ed08ddSLey Foon Tan case INST_STH: 109*82ed08ddSLey Foon Tan val = get_reg_val(fp, b); 110*82ed08ddSLey Foon Tan d1 = val >> 8; 111*82ed08ddSLey Foon Tan d0 = val >> 0; 112*82ed08ddSLey Foon Tan 113*82ed08ddSLey Foon Tan pr_debug("sth: ra=%d (%08x) rb=%d (%08x), imm16 %04x addr %08x val %08x\n", 114*82ed08ddSLey Foon Tan a, get_reg_val(fp, a), 115*82ed08ddSLey Foon Tan b, get_reg_val(fp, b), 116*82ed08ddSLey Foon Tan imm16, addr, val); 117*82ed08ddSLey Foon Tan 118*82ed08ddSLey Foon Tan if (in_kernel) { 119*82ed08ddSLey Foon Tan *(u8 *)(addr+0) = d0; 120*82ed08ddSLey Foon Tan *(u8 *)(addr+1) = d1; 121*82ed08ddSLey Foon Tan } else { 122*82ed08ddSLey Foon Tan fault |= __put_user(d0, (u8 *)(addr+0)); 123*82ed08ddSLey Foon Tan fault |= __put_user(d1, (u8 *)(addr+1)); 124*82ed08ddSLey Foon Tan } 125*82ed08ddSLey Foon Tan ma_half++; 126*82ed08ddSLey Foon Tan break; 127*82ed08ddSLey Foon Tan case INST_LDH: 128*82ed08ddSLey Foon Tan fault |= __get_user(d0, (u8 *)(addr+0)); 129*82ed08ddSLey Foon Tan fault |= __get_user(d1, (u8 *)(addr+1)); 130*82ed08ddSLey Foon Tan val = (short)((d1 << 8) | d0); 131*82ed08ddSLey Foon Tan put_reg_val(fp, b, val); 132*82ed08ddSLey Foon Tan ma_half++; 133*82ed08ddSLey Foon Tan break; 134*82ed08ddSLey Foon Tan case INST_STW: 135*82ed08ddSLey Foon Tan val = get_reg_val(fp, b); 136*82ed08ddSLey Foon Tan d3 = val >> 24; 137*82ed08ddSLey Foon Tan d2 = val >> 16; 138*82ed08ddSLey Foon Tan d1 = val >> 8; 139*82ed08ddSLey Foon Tan d0 = val >> 0; 140*82ed08ddSLey Foon Tan if (in_kernel) { 141*82ed08ddSLey Foon Tan *(u8 *)(addr+0) = d0; 142*82ed08ddSLey Foon Tan *(u8 *)(addr+1) = d1; 143*82ed08ddSLey Foon Tan *(u8 *)(addr+2) = d2; 144*82ed08ddSLey Foon Tan *(u8 *)(addr+3) = d3; 145*82ed08ddSLey Foon Tan } else { 146*82ed08ddSLey Foon Tan fault |= __put_user(d0, (u8 *)(addr+0)); 147*82ed08ddSLey Foon Tan fault |= __put_user(d1, (u8 *)(addr+1)); 148*82ed08ddSLey Foon Tan fault |= __put_user(d2, (u8 *)(addr+2)); 149*82ed08ddSLey Foon Tan fault |= __put_user(d3, (u8 *)(addr+3)); 150*82ed08ddSLey Foon Tan } 151*82ed08ddSLey Foon Tan ma_word++; 152*82ed08ddSLey Foon Tan break; 153*82ed08ddSLey Foon Tan case INST_LDW: 154*82ed08ddSLey Foon Tan fault |= __get_user(d0, (u8 *)(addr+0)); 155*82ed08ddSLey Foon Tan fault |= __get_user(d1, (u8 *)(addr+1)); 156*82ed08ddSLey Foon Tan fault |= __get_user(d2, (u8 *)(addr+2)); 157*82ed08ddSLey Foon Tan fault |= __get_user(d3, (u8 *)(addr+3)); 158*82ed08ddSLey Foon Tan val = (d3 << 24) | (d2 << 16) | (d1 << 8) | d0; 159*82ed08ddSLey Foon Tan put_reg_val(fp, b, val); 160*82ed08ddSLey Foon Tan ma_word++; 161*82ed08ddSLey Foon Tan break; 162*82ed08ddSLey Foon Tan } 163*82ed08ddSLey Foon Tan } 164*82ed08ddSLey Foon Tan 165*82ed08ddSLey Foon Tan addr = RDCTL(CTL_BADADDR); 166*82ed08ddSLey Foon Tan cause >>= 2; 167*82ed08ddSLey Foon Tan 168*82ed08ddSLey Foon Tan if (fault) { 169*82ed08ddSLey Foon Tan if (in_kernel) { 170*82ed08ddSLey Foon Tan pr_err("fault during kernel misaligned fixup @ %#lx; addr 0x%08x; isn=0x%08x\n", 171*82ed08ddSLey Foon Tan fp->ea, (unsigned int)addr, 172*82ed08ddSLey Foon Tan (unsigned int)isn); 173*82ed08ddSLey Foon Tan } else { 174*82ed08ddSLey Foon Tan pr_err("fault during user misaligned fixup @ %#lx; isn=%08x addr=0x%08x sp=0x%08lx pid=%d\n", 175*82ed08ddSLey Foon Tan fp->ea, 176*82ed08ddSLey Foon Tan (unsigned int)isn, addr, fp->sp, 177*82ed08ddSLey Foon Tan current->pid); 178*82ed08ddSLey Foon Tan 179*82ed08ddSLey Foon Tan _exception(SIGSEGV, fp, SEGV_MAPERR, fp->ea); 180*82ed08ddSLey Foon Tan return; 181*82ed08ddSLey Foon Tan } 182*82ed08ddSLey Foon Tan } 183*82ed08ddSLey Foon Tan 184*82ed08ddSLey Foon Tan /* 185*82ed08ddSLey Foon Tan * kernel mode - 186*82ed08ddSLey Foon Tan * note exception and skip bad instruction (return) 187*82ed08ddSLey Foon Tan */ 188*82ed08ddSLey Foon Tan if (in_kernel) { 189*82ed08ddSLey Foon Tan ma_kern++; 190*82ed08ddSLey Foon Tan fp->ea += 4; 191*82ed08ddSLey Foon Tan 192*82ed08ddSLey Foon Tan if (ma_usermode & KM_WARN) { 193*82ed08ddSLey Foon Tan pr_err("kernel unaligned access @ %#lx; BADADDR 0x%08x; cause=%d, isn=0x%08x\n", 194*82ed08ddSLey Foon Tan fp->ea, 195*82ed08ddSLey Foon Tan (unsigned int)addr, cause, 196*82ed08ddSLey Foon Tan (unsigned int)isn); 197*82ed08ddSLey Foon Tan /* show_regs(fp); */ 198*82ed08ddSLey Foon Tan } 199*82ed08ddSLey Foon Tan 200*82ed08ddSLey Foon Tan return; 201*82ed08ddSLey Foon Tan } 202*82ed08ddSLey Foon Tan 203*82ed08ddSLey Foon Tan ma_user++; 204*82ed08ddSLey Foon Tan 205*82ed08ddSLey Foon Tan /* 206*82ed08ddSLey Foon Tan * user mode - 207*82ed08ddSLey Foon Tan * possibly warn, 208*82ed08ddSLey Foon Tan * possibly send SIGBUS signal to process 209*82ed08ddSLey Foon Tan */ 210*82ed08ddSLey Foon Tan if (ma_usermode & UM_WARN) { 211*82ed08ddSLey Foon Tan pr_err("user unaligned access @ %#lx; isn=0x%08lx ea=0x%08lx ra=0x%08lx sp=0x%08lx\n", 212*82ed08ddSLey Foon Tan (unsigned long)addr, (unsigned long)isn, 213*82ed08ddSLey Foon Tan fp->ea, fp->ra, fp->sp); 214*82ed08ddSLey Foon Tan } 215*82ed08ddSLey Foon Tan 216*82ed08ddSLey Foon Tan if (ma_usermode & UM_SIGNAL) 217*82ed08ddSLey Foon Tan _exception(SIGBUS, fp, BUS_ADRALN, fp->ea); 218*82ed08ddSLey Foon Tan else 219*82ed08ddSLey Foon Tan fp->ea += 4; /* else advance */ 220*82ed08ddSLey Foon Tan } 221*82ed08ddSLey Foon Tan 222*82ed08ddSLey Foon Tan static void __init misaligned_calc_reg_offsets(void) 223*82ed08ddSLey Foon Tan { 224*82ed08ddSLey Foon Tan int i, r, offset; 225*82ed08ddSLey Foon Tan 226*82ed08ddSLey Foon Tan /* pre-calc offsets of registers on sys call stack frame */ 227*82ed08ddSLey Foon Tan offset = 0; 228*82ed08ddSLey Foon Tan 229*82ed08ddSLey Foon Tan /* struct pt_regs */ 230*82ed08ddSLey Foon Tan for (i = 0; i < 16; i++) { 231*82ed08ddSLey Foon Tan r = sys_stack_frame_reg_offset[i]; 232*82ed08ddSLey Foon Tan reg_offsets[r] = offset; 233*82ed08ddSLey Foon Tan offset += 4; 234*82ed08ddSLey Foon Tan } 235*82ed08ddSLey Foon Tan 236*82ed08ddSLey Foon Tan /* struct switch_stack */ 237*82ed08ddSLey Foon Tan offset = -sizeof(struct switch_stack); 238*82ed08ddSLey Foon Tan for (i = 16; i < 32; i++) { 239*82ed08ddSLey Foon Tan r = sys_stack_frame_reg_offset[i]; 240*82ed08ddSLey Foon Tan reg_offsets[r] = offset; 241*82ed08ddSLey Foon Tan offset += 4; 242*82ed08ddSLey Foon Tan } 243*82ed08ddSLey Foon Tan } 244*82ed08ddSLey Foon Tan 245*82ed08ddSLey Foon Tan 246*82ed08ddSLey Foon Tan static int __init misaligned_init(void) 247*82ed08ddSLey Foon Tan { 248*82ed08ddSLey Foon Tan /* default mode - silent fix */ 249*82ed08ddSLey Foon Tan ma_usermode = UM_FIXUP | KM_WARN; 250*82ed08ddSLey Foon Tan 251*82ed08ddSLey Foon Tan misaligned_calc_reg_offsets(); 252*82ed08ddSLey Foon Tan 253*82ed08ddSLey Foon Tan return 0; 254*82ed08ddSLey Foon Tan } 255*82ed08ddSLey Foon Tan 256*82ed08ddSLey Foon Tan fs_initcall(misaligned_init); 257