blob: 4deb21e08c7de65d517a14d14cf15d10bf3d77a9 [file] [log] [blame]
/*
* Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* vineetg: May 2011
* -user-space unaligned access emulation
*
* Vineetg: June 10th 2008
* -Added show_callee_regs to display CALLEE REGS
* -Added show_fault_diagnostics as a common function to display all
* the REGS, trigger event logging etc
*
* Rahul Trivedi: Codito Technologies 2004
*/
#include <linux/sched.h>
#include <linux/kdebug.h>
#include <asm/event-log.h>
#include <asm/uaccess.h>
#include <asm/board/unaligned_accounting.h>
extern int fixup_exception(struct pt_regs *regs);
void show_kernel_fault_diag(const char *str, struct pt_regs *regs,
unsigned long address, unsigned long cause_reg);
/* "volatile" because it causes compiler to optimize away code.
* Since running_on_hw is init to 1 at compile time, with -O2 compiler
* throws away the code in die( ) which is a problem on ISS
*/
volatile int running_on_hw = 1;
void die(const char *str, struct pt_regs *regs, unsigned long address,
unsigned long cause_reg)
{
if (running_on_hw) {
show_kernel_fault_diag(str, regs, address, cause_reg);
}
// DEAD END
__asm__("flag 1");
}
static int noinline do_fatal_exception(unsigned long cause, char *str,
struct pt_regs *regs, siginfo_t * info)
{
if (user_mode(regs)) {
struct task_struct *tsk = current;
tsk->thread.fault_address = (unsigned int)info->si_addr;
tsk->thread.cause_code = cause;
force_sig_info(info->si_signo, info, tsk);
} else {
/* Are we prepared to handle this kernel fault?
*
* (The kernel has valid exception-points in the source
* when it acesses user-memory. When it fails in one
* of those points, we find it in a table and do a jump
* to some fixup code that loads an appropriate error
* code)
*/
if (fixup_exception(regs))
return 0;
/*
* Oops. The kernel tried to access some bad page.
* We'll have to terminate things with extreme prejudice.
*/
die(str, regs, (unsigned long)info->si_addr, cause);
}
return 1;
}
#define DO_ERROR_INFO(signr, str, name, sicode) \
int name(unsigned long cause, unsigned long address, struct pt_regs *regs) \
{ \
siginfo_t info;\
info.si_signo = signr;\
info.si_errno = 0;\
info.si_code = sicode;\
info.si_addr = (void *)address;\
return do_fatal_exception(cause,str,regs,&info);\
}
#ifdef CONFIG_ARC_MISALIGNED_ACCESS
//#define DBG_MISALIGNED_FIXUP
#ifdef DBG_MISALIGNED_FIXUP
#define DBG_MISALIGNED_FIXUP_BUFSIZE 1024
#define DBG_BUF(args...) \
if ((__dbg_p - __debug_buf) < DBG_MISALIGNED_FIXUP_BUFSIZE - 80) { \
__dbg_p += sprintf(__dbg_p, "%s %d: ", __FUNCTION__, __LINE__); \
__dbg_p += sprintf(__dbg_p, args); \
}
#define DBG(args...) DBG_BUF(args)
#else
#define DBG_PRINT(args...) do { \
printk(KERN_INFO "%s %d: ", __FUNCTION__, __LINE__); \
printk(args); \
} while (0)
//#define DBG(args...) DBG_PRINT(args)
#define DBG(args...)
#endif
#define INST16_OPCODE_START 0xc
#define REG_LIMM 62
#define __get8_unaligned_check(val,addr,err) \
__asm__( \
"1: ldb.ab %1, [%2, 1]\n" \
"2:\n" \
" .section .fixup,\"ax\"\n" \
" .align 4\n" \
"3: mov %0, 1\n" \
" b 2b\n" \
" .previous\n" \
" .section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 1b, 3b\n" \
" .previous\n" \
: "=r" (err), "=&r" (val), "=r" (addr) \
: "0" (err), "2" (addr))
#define get16_unaligned_check(val,addr) \
do { \
unsigned int err = 0, v, a = addr; \
__get8_unaligned_check(v,a,err); \
val = v ; \
__get8_unaligned_check(v,a,err); \
val |= v << 8; \
if (err) \
goto fault; \
} while (0)
#define get32_unaligned_check(val,addr) \
do { \
unsigned int err = 0, v, a = addr; \
__get8_unaligned_check(v,a,err); \
val = v << 0; \
__get8_unaligned_check(v,a,err); \
val |= v << 8; \
__get8_unaligned_check(v,a,err); \
val |= v << 16; \
__get8_unaligned_check(v,a,err); \
val |= v << 24; \
if (err) \
goto fault; \
} while (0)
#define put16_unaligned_check(val,addr) \
do { \
unsigned int err = 0, v = val, a = addr; \
__asm__( \
"1: stb.ab %1, [%2, 1]\n" \
" lsr %1, %1, 8\n" \
"2: stb %1, [%2]\n" \
"3:\n" \
" .section .fixup,\"ax\"\n" \
" .align 4\n" \
"4: mov %0, 1\n" \
" b 3b\n" \
" .previous\n" \
" .section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 1b, 4b\n" \
" .long 2b, 4b\n" \
" .previous\n" \
: "=r" (err), "=&r" (v), "=&r" (a) \
: "0" (err), "1" (v), "2" (a)); \
if (err) \
goto fault; \
} while (0)
#define put32_unaligned_check(val,addr) \
do { \
unsigned int err = 0, v = val, a = addr; \
__asm__( \
"1: stb.ab %1, [%2, 1]\n" \
" lsr %1, %1, 8\n" \
"2: stb.ab %1, [%2, 1]\n" \
" lsr %1, %1, 8\n" \
"3: stb.ab %1, [%2, 1]\n" \
" lsr %1, %1, 8\n" \
"4: stb %1, [%2]\n" \
"5:\n" \
" .section .fixup,\"ax\"\n" \
" .align 4\n" \
"6: mov %0, 1\n" \
" b 5b\n" \
" .previous\n" \
" .section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 1b, 6b\n" \
" .long 2b, 6b\n" \
" .long 3b, 6b\n" \
" .long 4b, 6b\n" \
" .previous\n" \
: "=r" (err), "=&r" (v), "=&r" (a) \
: "0" (err), "1" (v), "2" (a)); \
if (err) \
goto fault; \
} while (0)
/* 16 bit instructions use the registers r0 r1 r2 r3 r12 r13 r14 r15. */
/* e.g. if reg from the instruction = 5, we want r13 */
static inline unsigned long fix_reg16(const unsigned int reg)
{
if (reg & 0x04)
return reg | 0x08;
return reg;
}
static int get_reg(unsigned int reg, struct pt_regs *regs, struct callee_regs *cregs, long *val, bool inst_16)
{
long *p;
if (inst_16)
reg = fix_reg16(reg);
if (reg <= 12) {
p = &regs->r0;
*val = p[-reg];
return 0;
}
if (reg <= 25) {
p = &cregs->r13;
*val = p[13-reg];
return 0;
}
if (reg == 26) {
*val = regs->r26;
return 0;
}
if (reg == 27) {
*val = regs->fp;
return 0;
}
if (reg == 28) {
*val = regs->sp;
return 0;
}
if (reg == 31) {
*val = regs->blink;
return 0;
}
return 1;
}
static int set_reg(unsigned int reg, long val, struct pt_regs *regs, struct callee_regs *cregs, bool inst_16)
{
long *p;
if (inst_16)
reg = fix_reg16(reg);
if (reg <= 12) {
p = &regs->r0;
p[-reg] = val;
return 0;
}
if (reg <= 25) {
p = &cregs->r13;
p[13-reg] = val;
return 0;
}
if (reg == 26) {
regs->r26 = val;
return 0;
}
if (reg == 27) {
regs->fp = val;
return 0;
}
if (reg == 28) {
regs->sp = val;
return 0;
}
if (reg == 31) {
regs->blink = val;
return 0;
}
return 1;
}
#define get_limm(x) \
do { \
if (usermode) { \
if (copy_from_user(&x, (u32 *)(instrptr + 4), 4)) \
goto fault; \
} else { \
x = *((unsigned*)(instrptr + 4)); \
} \
x = ((x & 0xffff) << 16) | (x >> 16); \
} while(0)
/* accounting for unaligned accesses */
struct unaligned_access_accounting unaligned_access_stats = {0};
EXPORT_SYMBOL(unaligned_access_stats);
static int misaligned_fixup(const unsigned long address,
struct pt_regs *regs, const unsigned long cause, struct callee_regs *cregs)
{
unsigned long instrptr;
unsigned long instr, instr_lo;
bool inst_16 = true;
long src1, src2, src3 = 0, dest = 0, val;
unsigned zz, aa, write, x, pref, limm, di, wb_reg = 0;
#ifdef DBG_MISALIGNED_FIXUP
char __debug_buf[DBG_MISALIGNED_FIXUP_BUFSIZE] = {0};
char *__dbg_p = __debug_buf;
src1 = 0, src2 = 0, src3 = 0, dest = 0, val = 0;
zz = 0, aa = 0, write = 0, x = 0, pref = 0, limm = 0, di = 0, wb_reg = 0;
#endif
struct unaligned_access_accounting* stats = &unaligned_access_stats;
/* set cregs for fault printout */
current->thread.callee_reg = (unsigned long)cregs;
const int usermode = user_mode(regs);
di = 0;
aa = 0;
write = 0;
x = 0;
pref = 0;
limm = 0;
instr = 0;
instrptr = instruction_pointer(regs);
if (usermode) {
if (get_user(instr, (u16 *)(instrptr & ~1))) {
goto fault;
}
} else {
instr = *((u16 *)(instrptr & ~1));
}
inst_16 = (((instr >> 11) & 0x1f) >= INST16_OPCODE_START);
if (!inst_16) {
if (usermode) {
if (get_user(instr_lo, (u16 *)((instrptr & ~1) + 2))) {
goto fault;
}
} else {
instr_lo = *((u16 *)((instrptr & ~1) + 2));
}
instr = (instr << 16) | instr_lo;
}
DBG(KERN_ERR "alignment fix: 0x%0*lx at [<0x%08lx>] usermode: %d delay slot: %d\n",
inst_16 ? 4 : 8,
instr, instrptr, usermode, delay_mode(regs));
/* rate limited complaint about faulting userland programs */
if (usermode && printk_ratelimit())
printk(KERN_ERR "Misaligned access trap for user program '%s' (parent '%s'): instr: 0x%0*lx at [<0x%08lx>]\n",
current->comm, current->parent ? current->parent->comm : "<none>",
inst_16 ? 4 : 8, instr, instrptr);
/* accounting */
if (usermode) {
stats->user++;
} else {
stats->kernel_iptr[stats->kernel % UNALIGNED_INSTPTR_BUFSIZE] = instrptr;
stats->kernel++;
}
if (inst_16)
stats->inst_16++;
else
stats->inst_32++;
/* instruction decoding */
if (inst_16) {
/* 16 bit instruction */
const int decode = (instr >> 11) & 0x1f;
DBG("decode=0x%02x\n", decode);
stats->inst[decode % UNALIGNED_INST_OPCODES]++;
switch (decode) {
case 0x0c: /* LD_S|LDB_S|LDW_S a,[b,c] */
zz = (instr >> 3) & 3;
src1 = (instr >> 8) & 7;
src2 = (instr >> 5) & 7;
dest = instr & 7;
if ((get_reg(src1, regs, cregs, &src1, inst_16)) ||
(get_reg(src2, regs, cregs, &src2, inst_16)))
goto bad;
break;
case 0x10: /* LD_S c,[b,u7] */
zz = 0;
src1 = (instr >> 8) & 7;
src2 = (instr & 0x1f) << 2;
dest = (instr >> 5) & 7;
//DBG("0x10: src1 %ld src2 %ld dest %ld\n", src1, src2, dest);
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
//DBG("0x10: src1 0x%08lx\n", src1);
break;
case 0x11: /* LDB_S c,[b,u5] */
zz = 1;
break;
case 0x12: /* LDW_S c,[b,u6] */
zz = 2;
src1 = (instr >> 8) & 7;
src2 = (instr & 0x1f) << 1;
dest = (instr >> 5) & 7;
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
break;
case 0x13: /* LDW_S.X c,[b,u6] */
x = 1;
zz = 2;
src1 = (instr >> 8) & 7;
src2 = (instr & 0x1f) << 1;
dest = (instr >> 5) & 7;
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
break;
case 0x14: /* ST_S c,[b,u7] */
write = 1;
zz = 0;
src1 = (instr >> 5) & 7;
src2 = (instr >> 8) & 7;
src3 = (instr & 0x1f) << 2;
if ((get_reg(src1, regs, cregs, &src1, inst_16)) ||
(get_reg(src2, regs, cregs, &src2, inst_16)))
goto bad;
break;
case 0x15: /* STB_S c,[b,u6] */
zz = 1; /* STB should not have unaligned exception */
break;
case 0x16: /* STW_S c,[b,u6] */
write = 1;
zz = 2;
src1 = (instr >> 5) & 7;
src2 = (instr >> 8) & 7;
src3 = (instr & 0x1f) << 1;
if ((get_reg(src1, regs, cregs, &src1, inst_16)) ||
(get_reg(src2, regs, cregs, &src2, inst_16)))
goto bad;
break;
case 0x18: /* LD_S|LDB_S b,[sp,u7], ST_S|STB_S b,[sp,u7] */
write = (instr >> 6) & 1;
zz = (instr >> 5) & 1;
if (zz == 1)
break;
if (write == 0) {
src1 = 28;
src2 = (instr & 0x1f) << 2;
dest = (instr >> 8) & 7;
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
} else {
src1 = (instr >> 8) & 7;
src2 = 28;
src3 = (instr & 0x1f) << 2;
if ((get_reg(src1, regs, cregs, &src1, inst_16)) ||
(get_reg(src2, regs, cregs, &src2, inst_16)))
goto bad;
}
break;
case 0x19: /* LD_S|LDB_S|LDW_S r0,[gp,s11/s9/s10] */
zz = (instr >> 9) & 3;
src1 = 26;
src2 = instr & 0x1ff;
if (zz == 0)
src2 = (src2 << 23) >> 21; /* s11 */
else if (zz == 1)
break;
else if (zz == 2)
src2 = (src2 << 23) >> 22; /* s10 */
dest = 0;
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
break;
case 0x1a: /* LD_S b,[pcl,u10] */
zz = 0;
src1 = regs->ret;
src2 = (instr & 0xff) << 2;
dest = (instr >> 8) & 7;
break;
default:
goto bad;
}
} else {
/* 32 bit instruction */
const int decode = (instr >> 27) & 0x1f;
DBG("decode=0x%02x\n", decode);
stats->inst[decode % UNALIGNED_INST_OPCODES]++;
switch (decode) {
case 0x02: /* LD<zz> a,[b,s9] */
di = (instr >> 11) & 1;
if (di)
break;
x = (instr >> 6) & 1;
zz = (instr >> 7) & 3;
aa = (instr >> 9) & 3;
wb_reg = (((instr >> 12) & 7) << 3) | ((instr >> 24) & 7);
if (wb_reg == REG_LIMM) {
limm = 1;
aa = 0;
get_limm(src1);
} else {
if (get_reg(wb_reg, regs, cregs, &src1, inst_16))
goto bad;
}
src2 = ((instr >> 16) & 0xff) | (((instr >> 15) & 1) << 8);
src2 = (src2 << 23) >> 23;
dest = instr & 0x3f;
if (dest == REG_LIMM) {
pref = 1;
break;
}
break;
case 0x03: /* ST<zz> c,[b,s9] */
write = 1;
di = (instr >> 5) & 1;
if (di)
break;
aa = (instr >> 3) & 3;
zz = (instr >> 1) & 3;
src1 = (instr >> 6) & 0x3f;
//DBG("32 bit st: src1 reg %ld\n", src1);
if (src1 == REG_LIMM) {
limm = 1;
get_limm(src1);
} else {
if (get_reg(src1, regs, cregs, &src1, inst_16))
goto bad;
}
//DBG("32 bit st: src1 val %ld\n", src1);
wb_reg = (((instr >> 12) & 7) << 3) | ((instr >> 24) & 7);
if (wb_reg == REG_LIMM) {
aa = 0;
limm = 1;
get_limm(src2);
} else {
if (get_reg(wb_reg, regs, cregs, &src2, inst_16))
goto bad;
}
src3 = ((instr >> 16) & 0xff) | (((instr >> 15) & 1) << 8);
src3 = (src3 << 23) >> 23;
break;
case 0x04: /* LD<zz> a,[b,c] */
di = (instr >> 15) & 1;
if (di)
break;
x = (instr >> 16) & 1;
zz = (instr >> 17) & 3;
aa = (instr >> 22) & 3;
wb_reg = (((instr >> 12) & 7) << 3) | ((instr >> 24) & 7);
if (wb_reg == REG_LIMM) {
limm = 1;
get_limm(src1);
} else {
if (get_reg(wb_reg, regs, cregs, &src1, inst_16))
goto bad;
}
src2 = (instr >> 6) & 0x3f;
if (src2 == REG_LIMM) {
limm = 1;
get_limm(src2);
} else {
if (get_reg(src2, regs, cregs, &src2, inst_16))
goto bad;
}
dest = instr & 0x3f;
if (dest == REG_LIMM)
pref = 1;
break;
default:
goto bad;
}
}
/* ldb/stb should not have unaligned exception */
if ((zz == 1) || (di))
goto bad;
DBG("write %d aa %d zz %d pref %d wb_reg %d src1 0x%lx src2 0x%lx src3 %ld dest %ld val 0x%08lx\n",
write, aa, zz, pref, wb_reg, src1, src2, src3, dest, val);
/* ld instructions */
if (write == 0) {
/* register write back */
if ((aa == 1) || (aa == 2)) {
if (set_reg(wb_reg, src1 + src2, regs, cregs, inst_16))
goto bad;
if (aa == 2)
src2 = 0;
}
unsigned long faulting_address = src1 + src2;
if (aa == 3 && zz == 2)
faulting_address = src1 + (src2 << 1);
else if (aa == 3 && zz == 0)
faulting_address = src1 + (src2 << 2);
if (faulting_address != address)
printk(KERN_ERR "%s ld address 0x%lx does not match those of operands: s1/s2: 0x%lx 0x%lx\n",
__FUNCTION__, address, src1, src2);
if (zz == 0)
get32_unaligned_check(val, faulting_address);
else {
get16_unaligned_check(val, faulting_address);
if (x)
val = (val << 16) >> 16;
}
if (pref == 0)
if (set_reg(dest, val, regs, cregs, inst_16))
goto bad;
/* store instructions */
} else {
/* register write back */
if ((aa == 1) || (aa == 2)) {
if (set_reg(wb_reg, src2 + src3, regs, cregs, inst_16))
goto bad;
if (aa == 2)
src3 = 0;
}
unsigned long faulting_address = src2 + src3;
if (aa == 3 && zz == 2)
faulting_address = src2 + (src3 << 1);
else if (aa == 3 && zz == 0)
faulting_address = src2 + (src3 << 2);
if (faulting_address != address)
printk(KERN_ERR "%s st address 0x%lx does not match those of operands: s2/s3: 0x%lx 0x%lx\n",
__FUNCTION__, address, src2, src3);
/* write fix-up */
if (zz == 0)
put32_unaligned_check(src1, faulting_address);
else
put16_unaligned_check(src1, faulting_address);
}
/* accounting */
if (write)
stats->write++;
else
stats->read++;
if (zz == 0)
stats->word++;
else
stats->half++;
if (delay_mode(regs)) {
regs->ret = regs->bta & ~0x1;
regs->status32 &= ~STATUS_DE_MASK;
} else {
regs->ret += (inst_16) ? 2 :
(limm) ? 8 : 4;
}
return 0;
bad:
#ifdef DBG_MISALIGNED_FIXUP
printk(KERN_ERR "%s\n", __debug_buf);
#endif
/*
* Oops, we didn't handle the instruction.
*/
printk(KERN_ERR "Alignment trap: not handling instruction "
"0x%0*lx at [<0x%08lx>] usermode: %d\n",
inst_16 ? 4 : 8,
instr, instrptr, usermode);
stats->skipped++;
return 1;
fault:
#ifdef DBG_MISALIGNED_FIXUP
printk(KERN_ERR "%s\n", __debug_buf);
#endif
printk(KERN_ERR "Alignment trap: fault in fix-up "
"0x%0*lx at [<0x%08lx>] usermode: %d\n",
inst_16 ? 4 : 8,
instr, instrptr, usermode);
stats->skipped++;
return 1;
}
int do_misaligned_access(unsigned long cause, unsigned long address,
struct pt_regs *regs, struct callee_regs *cregs)
{
if (misaligned_fixup(address, regs, cause, cregs) != 0) {
siginfo_t info;
info.si_signo = SIGSEGV;
info.si_errno = 0;
info.si_code = SEGV_ACCERR;
info.si_addr = (void *)address;
return do_fatal_exception(cause,"Misaligned Access", regs,&info);
}
return 0;
}
#else
DO_ERROR_INFO(SIGSEGV, "Misaligned Access", do_misaligned_access, SEGV_ACCERR)
#endif
DO_ERROR_INFO(SIGILL, "Privileged Operation/Disabled Extension/Actionpoint Hit",
do_privilege_fault, ILL_PRVOPC)
DO_ERROR_INFO(SIGILL, "Extenion Instruction Exception",
do_extension_fault, ILL_ILLOPC)
DO_ERROR_INFO(SIGILL, "Illegal Instruction/Illegal Instruction Sequence",
do_instruction_error, ILL_ILLOPC)
DO_ERROR_INFO(SIGBUS, "Access to Invalid Memory", do_memory_error, BUS_ADRERR)
DO_ERROR_INFO(SIGTRAP, "Breakpoint Set", do_trap_is_brkpt, TRAP_BRKPT)
void do_machine_check_fault( unsigned long cause, unsigned long address,
struct pt_regs *regs)
{
die("Machine Check Exception",regs, address, cause);
}
void __init trap_init(void)
{
return;
}
asmlinkage void do_trap_is_kprobe(unsigned long cause, unsigned long address,
struct pt_regs *regs)
{
notify_die(DIE_TRAP, "kprobe_trap", regs, address, cause, SIGTRAP);
}
asmlinkage void do_trap(unsigned long cause, unsigned long address,
struct pt_regs *regs)
{
unsigned int param = cause & 0xff;
switch(param)
{
case 1:
do_trap_is_brkpt(cause, address, regs);
break;
case 2:
do_trap_is_kprobe(param, address, regs);
break;
default:
break;
}
}
asmlinkage void do_insterror_or_kprobe(unsigned long cause,
unsigned long address, struct pt_regs *regs)
{
/* Check if this exception is caused by kprobes */
if(notify_die(DIE_IERR, "kprobe_ierr", regs, address,
cause, SIGILL) == NOTIFY_STOP)
return;
do_instruction_error(cause, address, regs);
}