diff options
Diffstat (limited to 'arch/sh/kernel/traps_32.c')
-rw-r--r-- | arch/sh/kernel/traps_32.c | 223 |
1 files changed, 174 insertions, 49 deletions
diff --git a/arch/sh/kernel/traps_32.c b/arch/sh/kernel/traps_32.c index 2b772776fcd..69bb1652ecc 100644 --- a/arch/sh/kernel/traps_32.c +++ b/arch/sh/kernel/traps_32.c @@ -24,6 +24,7 @@ #include <linux/kdebug.h> #include <linux/kexec.h> #include <linux/limits.h> +#include <linux/proc_fs.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/fpu.h> @@ -44,6 +45,85 @@ #define TRAP_ILLEGAL_SLOT_INST 13 #endif +static unsigned long se_user; +static unsigned long se_sys; +static unsigned long se_half; +static unsigned long se_word; +static unsigned long se_dword; +static unsigned long se_multi; +/* bitfield: 1: warn 2: fixup 4: signal -> combinations 2|4 && 1|2|4 are not + valid! */ +static int se_usermode = 3; +/* 0: no warning 1: print a warning message */ +static int se_kernmode_warn = 1; + +#ifdef CONFIG_PROC_FS +static const char *se_usermode_action[] = { + "ignored", + "warn", + "fixup", + "fixup+warn", + "signal", + "signal+warn" +}; + +static int +proc_alignment_read(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + char *p = page; + int len; + + p += sprintf(p, "User:\t\t%lu\n", se_user); + p += sprintf(p, "System:\t\t%lu\n", se_sys); + p += sprintf(p, "Half:\t\t%lu\n", se_half); + p += sprintf(p, "Word:\t\t%lu\n", se_word); + p += sprintf(p, "DWord:\t\t%lu\n", se_dword); + p += sprintf(p, "Multi:\t\t%lu\n", se_multi); + p += sprintf(p, "User faults:\t%i (%s)\n", se_usermode, + se_usermode_action[se_usermode]); + p += sprintf(p, "Kernel faults:\t%i (fixup%s)\n", se_kernmode_warn, + se_kernmode_warn ? "+warn" : ""); + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} + +static int proc_alignment_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char mode; + + if (count > 0) { + if (get_user(mode, buffer)) + return -EFAULT; + if (mode >= '0' && mode <= '5') + se_usermode = mode - '0'; + } + return count; +} + +static int proc_alignment_kern_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char mode; + + if (count > 0) { + if (get_user(mode, buffer)) + return -EFAULT; + if (mode >= '0' && mode <= '1') + se_kernmode_warn = mode - '0'; + } + return count; +} +#endif + static void dump_mem(const char *str, unsigned long bottom, unsigned long top) { unsigned long p; @@ -136,6 +216,7 @@ static void die_if_no_fixup(const char * str, struct pt_regs * regs, long err) regs->pc = fixup->fixup; return; } + die(str, regs, err); } } @@ -193,6 +274,13 @@ static int handle_unaligned_ins(insn_size_t instruction, struct pt_regs *regs, count = 1<<(instruction&3); + switch (count) { + case 1: se_half += 1; break; + case 2: se_word += 1; break; + case 4: se_dword += 1; break; + case 8: se_multi += 1; break; /* ??? */ + } + ret = -EFAULT; switch (instruction>>12) { case 0: /* mov.[bwl] to/from memory via r0+rn */ @@ -358,31 +446,28 @@ static inline int handle_delayslot(struct pt_regs *regs, #define SH_PC_8BIT_OFFSET(instr) ((((signed char)(instr))*2) + 4) #define SH_PC_12BIT_OFFSET(instr) ((((signed short)(instr<<4))>>3) + 4) -/* - * XXX: SH-2A needs this too, but it needs an overhaul thanks to mixed 32-bit - * opcodes.. - */ - -static int handle_unaligned_notify_count = 10; - int handle_unaligned_access(insn_size_t instruction, struct pt_regs *regs, - struct mem_access *ma) + struct mem_access *ma, int expected) { u_int rm; int ret, index; + /* + * XXX: We can't handle mixed 16/32-bit instructions yet + */ + if (instruction_size(instruction) != 2) + return -EINVAL; + index = (instruction>>8)&15; /* 0x0F00 */ rm = regs->regs[index]; - /* shout about the first ten userspace fixups */ - if (user_mode(regs) && handle_unaligned_notify_count>0) { - handle_unaligned_notify_count--; - - printk(KERN_NOTICE "Fixing up unaligned userspace access " + /* shout about fixups */ + if (!expected && printk_ratelimit()) + printk(KERN_NOTICE "Fixing up unaligned %s access " "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", + user_mode(regs) ? "userspace" : "kernel", current->comm, task_pid_nr(current), (void *)regs->pc, instruction); - } ret = -EFAULT; switch (instruction&0xF000) { @@ -538,24 +623,44 @@ asmlinkage void do_address_error(struct pt_regs *regs, local_irq_enable(); - /* bad PC is not something we can fix */ - if (regs->pc & 1) { - si_code = BUS_ADRALN; - goto uspace_segv; - } + se_user += 1; set_fs(USER_DS); - if (copy_from_user(&instruction, (void __user *)(regs->pc), + if (copy_from_user(&instruction, (insn_size_t *)(regs->pc & ~1), sizeof(instruction))) { - /* Argh. Fault on the instruction itself. - This should never happen non-SMP - */ set_fs(oldfs); goto uspace_segv; } + set_fs(oldfs); + + /* shout about userspace fixups */ + if (se_usermode & 1) + printk(KERN_NOTICE "Unaligned userspace access " + "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", + current->comm, current->pid, (void *)regs->pc, + instruction); + + if (se_usermode & 2) + goto fixup; + if (se_usermode & 4) + goto uspace_segv; + else { + /* ignore */ + regs->pc += instruction_size(instruction); + return; + } + +fixup: + /* bad PC is not something we can fix */ + if (regs->pc & 1) { + si_code = BUS_ADRALN; + goto uspace_segv; + } + + set_fs(USER_DS); tmp = handle_unaligned_access(instruction, regs, - &user_mem_access); + &user_mem_access, 0); set_fs(oldfs); if (tmp==0) @@ -571,6 +676,8 @@ uspace_segv: info.si_addr = (void __user *)address; force_sig_info(SIGBUS, &info, current); } else { + se_sys += 1; + if (regs->pc & 1) die("unaligned program counter", regs, error_code); @@ -584,7 +691,14 @@ uspace_segv: die("insn faulting in do_address_error", regs, 0); } - handle_unaligned_access(instruction, regs, &user_mem_access); + if (se_kernmode_warn) + printk(KERN_NOTICE "Unaligned kernel access " + "on behalf of \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", + current->comm, current->pid, (void *)regs->pc, + instruction); + + handle_unaligned_access(instruction, regs, + &user_mem_access, 0); set_fs(oldfs); } } @@ -858,30 +972,6 @@ void __init trap_init(void) per_cpu_trap_init(); } -void show_trace(struct task_struct *tsk, unsigned long *sp, - struct pt_regs *regs) -{ - unsigned long addr; - - if (regs && user_mode(regs)) - return; - - printk("\nCall trace:\n"); - - while (!kstack_end(sp)) { - addr = *sp++; - if (kernel_text_address(addr)) - print_ip_sym(addr); - } - - printk("\n"); - - if (!tsk) - tsk = current; - - debug_show_held_locks(tsk); -} - void show_stack(struct task_struct *tsk, unsigned long *sp) { unsigned long stack; @@ -904,3 +994,38 @@ void dump_stack(void) show_stack(NULL, NULL); } EXPORT_SYMBOL(dump_stack); + +#ifdef CONFIG_PROC_FS +/* + * This needs to be done after sysctl_init, otherwise sys/ will be + * overwritten. Actually, this shouldn't be in sys/ at all since + * it isn't a sysctl, and it doesn't contain sysctl information. + * We now locate it in /proc/cpu/alignment instead. + */ +static int __init alignment_init(void) +{ + struct proc_dir_entry *dir, *res; + + dir = proc_mkdir("cpu", NULL); + if (!dir) + return -ENOMEM; + + res = create_proc_entry("alignment", S_IWUSR | S_IRUGO, dir); + if (!res) + return -ENOMEM; + + res->read_proc = proc_alignment_read; + res->write_proc = proc_alignment_write; + + res = create_proc_entry("kernel_alignment", S_IWUSR | S_IRUGO, dir); + if (!res) + return -ENOMEM; + + res->read_proc = proc_alignment_read; + res->write_proc = proc_alignment_kern_write; + + return 0; +} + +fs_initcall(alignment_init); +#endif |