123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- /*
- * linux/arch/nios2/kernel/misaligned.c
- *
- * basic emulation for mis-aligned accesses on the NIOS II cpu
- * modelled after the version for arm in arm/alignment.c
- *
- * Brad Parker <brad@heeltoe.com>
- * Copyright (C) 2010 Ambient Corporation
- * Copyright (c) 2010 Altera Corporation, San Jose, California, USA.
- * Copyright (c) 2010 Arrow Electronics, Inc.
- *
- * This file is subject to the terms and conditions of the GNU General
- * Public License. See the file COPYING in the main directory of
- * this archive for more details.
- */
- #include <linux/errno.h>
- #include <linux/string.h>
- #include <linux/proc_fs.h>
- #include <linux/init.h>
- #include <linux/sched.h>
- #include <linux/uaccess.h>
- #include <linux/seq_file.h>
- #include <asm/traps.h>
- #include <asm/unaligned.h>
- /* instructions we emulate */
- #define INST_LDHU 0x0b
- #define INST_STH 0x0d
- #define INST_LDH 0x0f
- #define INST_STW 0x15
- #define INST_LDW 0x17
- static unsigned int ma_usermode;
- #define UM_WARN 0x01
- #define UM_FIXUP 0x02
- #define UM_SIGNAL 0x04
- #define KM_WARN 0x08
- /* see arch/nios2/include/asm/ptrace.h */
- static u8 sys_stack_frame_reg_offset[] = {
- /* struct pt_regs */
- 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 0,
- /* struct switch_stack */
- 16, 17, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0
- };
- static int reg_offsets[32];
- static inline u32 get_reg_val(struct pt_regs *fp, int reg)
- {
- u8 *p = ((u8 *)fp) + reg_offsets[reg];
- return *(u32 *)p;
- }
- static inline void put_reg_val(struct pt_regs *fp, int reg, u32 val)
- {
- u8 *p = ((u8 *)fp) + reg_offsets[reg];
- *(u32 *)p = val;
- }
- /*
- * (mis)alignment handler
- */
- asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause)
- {
- u32 isn, addr, val;
- int in_kernel;
- u8 a, b, d0, d1, d2, d3;
- s16 imm16;
- unsigned int fault;
- /* back up one instruction */
- fp->ea -= 4;
- if (fixup_exception(fp)) {
- return;
- }
- in_kernel = !user_mode(fp);
- isn = *(unsigned long *)(fp->ea);
- fault = 0;
- /* do fixup if in kernel or mode turned on */
- if (in_kernel || (ma_usermode & UM_FIXUP)) {
- /* decompose instruction */
- a = (isn >> 27) & 0x1f;
- b = (isn >> 22) & 0x1f;
- imm16 = (isn >> 6) & 0xffff;
- addr = get_reg_val(fp, a) + imm16;
- /* do fixup to saved registers */
- switch (isn & 0x3f) {
- case INST_LDHU:
- fault |= __get_user(d0, (u8 *)(addr+0));
- fault |= __get_user(d1, (u8 *)(addr+1));
- val = (d1 << 8) | d0;
- put_reg_val(fp, b, val);
- break;
- case INST_STH:
- val = get_reg_val(fp, b);
- d1 = val >> 8;
- d0 = val >> 0;
- if (in_kernel) {
- *(u8 *)(addr+0) = d0;
- *(u8 *)(addr+1) = d1;
- } else {
- fault |= __put_user(d0, (u8 *)(addr+0));
- fault |= __put_user(d1, (u8 *)(addr+1));
- }
- break;
- case INST_LDH:
- fault |= __get_user(d0, (u8 *)(addr+0));
- fault |= __get_user(d1, (u8 *)(addr+1));
- val = (short)((d1 << 8) | d0);
- put_reg_val(fp, b, val);
- break;
- case INST_STW:
- val = get_reg_val(fp, b);
- d3 = val >> 24;
- d2 = val >> 16;
- d1 = val >> 8;
- d0 = val >> 0;
- if (in_kernel) {
- *(u8 *)(addr+0) = d0;
- *(u8 *)(addr+1) = d1;
- *(u8 *)(addr+2) = d2;
- *(u8 *)(addr+3) = d3;
- } else {
- fault |= __put_user(d0, (u8 *)(addr+0));
- fault |= __put_user(d1, (u8 *)(addr+1));
- fault |= __put_user(d2, (u8 *)(addr+2));
- fault |= __put_user(d3, (u8 *)(addr+3));
- }
- break;
- case INST_LDW:
- fault |= __get_user(d0, (u8 *)(addr+0));
- fault |= __get_user(d1, (u8 *)(addr+1));
- fault |= __get_user(d2, (u8 *)(addr+2));
- fault |= __get_user(d3, (u8 *)(addr+3));
- val = (d3 << 24) | (d2 << 16) | (d1 << 8) | d0;
- put_reg_val(fp, b, val);
- break;
- }
- }
- addr = RDCTL(CTL_BADADDR);
- cause >>= 2;
- if (fault) {
- if (in_kernel) {
- pr_err("fault during kernel misaligned fixup @ %#lx; addr 0x%08x; isn=0x%08x\n",
- fp->ea, (unsigned int)addr,
- (unsigned int)isn);
- } else {
- pr_err("fault during user misaligned fixup @ %#lx; isn=%08x addr=0x%08x sp=0x%08lx pid=%d\n",
- fp->ea,
- (unsigned int)isn, addr, fp->sp,
- current->pid);
- _exception(SIGSEGV, fp, SEGV_MAPERR, fp->ea);
- return;
- }
- }
- /*
- * kernel mode -
- * note exception and skip bad instruction (return)
- */
- if (in_kernel) {
- fp->ea += 4;
- if (ma_usermode & KM_WARN) {
- pr_err("kernel unaligned access @ %#lx; BADADDR 0x%08x; cause=%d, isn=0x%08x\n",
- fp->ea,
- (unsigned int)addr, cause,
- (unsigned int)isn);
- /* show_regs(fp); */
- }
- return;
- }
- /*
- * user mode -
- * possibly warn,
- * possibly send SIGBUS signal to process
- */
- if (ma_usermode & UM_WARN) {
- pr_err("user unaligned access @ %#lx; isn=0x%08lx ea=0x%08lx ra=0x%08lx sp=0x%08lx\n",
- (unsigned long)addr, (unsigned long)isn,
- fp->ea, fp->ra, fp->sp);
- }
- if (ma_usermode & UM_SIGNAL)
- _exception(SIGBUS, fp, BUS_ADRALN, fp->ea);
- else
- fp->ea += 4; /* else advance */
- }
- static void __init misaligned_calc_reg_offsets(void)
- {
- int i, r, offset;
- /* pre-calc offsets of registers on sys call stack frame */
- offset = 0;
- /* struct pt_regs */
- for (i = 0; i < 16; i++) {
- r = sys_stack_frame_reg_offset[i];
- reg_offsets[r] = offset;
- offset += 4;
- }
- /* struct switch_stack */
- offset = -sizeof(struct switch_stack);
- for (i = 16; i < 32; i++) {
- r = sys_stack_frame_reg_offset[i];
- reg_offsets[r] = offset;
- offset += 4;
- }
- }
- static int __init misaligned_init(void)
- {
- /* default mode - silent fix */
- ma_usermode = UM_FIXUP | KM_WARN;
- misaligned_calc_reg_offsets();
- return 0;
- }
- fs_initcall(misaligned_init);
|