diff options
author | Roman Yeryomin <roman@advem.lv> | 2012-09-13 00:40:35 +0300 |
---|---|---|
committer | Roman Yeryomin <roman@advem.lv> | 2012-12-03 00:13:21 +0200 |
commit | 5deb3317cb51ac52de922bb55f8492624018906d (patch) | |
tree | c2fbe6346699d9bb0f2100490c3029519bb8fde8 /target/linux/realtek/files/arch/rlx/kernel/traps.c | |
parent | 0239d37124f9184b478a42de8a7fa1bc85a6a6fe (diff) |
Add realtek target files
Signed-off-by: Roman Yeryomin <roman@advem.lv>
Diffstat (limited to 'target/linux/realtek/files/arch/rlx/kernel/traps.c')
-rw-r--r-- | target/linux/realtek/files/arch/rlx/kernel/traps.c | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/target/linux/realtek/files/arch/rlx/kernel/traps.c b/target/linux/realtek/files/arch/rlx/kernel/traps.c new file mode 100644 index 000000000..f75fd5291 --- /dev/null +++ b/target/linux/realtek/files/arch/rlx/kernel/traps.c @@ -0,0 +1,735 @@ +/* + * 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. + * + * Copyright (C) 1994 - 1999, 2000, 01, 06 Ralf Baechle + * Copyright (C) 1995, 1996 Paul M. Antoine + * Copyright (C) 1998 Ulf Carlsson + * Copyright (C) 1999 Silicon Graphics, Inc. + * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com + * Copyright (C) 2000, 01 MIPS Technologies, Inc. + * Copyright (C) 2002, 2003, 2004, 2005, 2007 Maciej W. Rozycki + */ +#include <linux/bug.h> +#include <linux/compiler.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/spinlock.h> +#include <linux/kallsyms.h> +#include <linux/bootmem.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/kgdb.h> +#include <linux/kdebug.h> + +#include <asm/bootinfo.h> +#include <asm/branch.h> +#include <asm/break.h> +#include <asm/cpu.h> +#include <asm/rlxregs.h> +#include <asm/module.h> +#include <asm/pgtable.h> +#include <asm/ptrace.h> +#include <asm/sections.h> +#include <asm/system.h> +#include <asm/tlbdebug.h> +#include <asm/traps.h> +#include <asm/uaccess.h> +#include <asm/mmu_context.h> +#include <asm/types.h> +#include <asm/stacktrace.h> +#include <asm/irq.h> + +#include <linux/time.h> +#include <linux/kernel_stat.h> +#include <asm/cputime.h> + +#ifdef CONFIG_PANIC_PRINTK +#define printk panic_printk +#endif + +extern asmlinkage void rlx_irq_dispatch(void); +extern asmlinkage void handle_tlbm(void); +extern asmlinkage void handle_tlbl(void); +extern asmlinkage void handle_tlbs(void); +extern asmlinkage void handle_adel(void); +extern asmlinkage void handle_ades(void); +extern asmlinkage void handle_sys(void); +extern asmlinkage void handle_bp(void); +extern asmlinkage void handle_ri(void); +extern asmlinkage void handle_cpu(void); +extern asmlinkage void handle_ov(void); +extern asmlinkage void handle_reserved(void); + +static void show_raw_backtrace(unsigned long reg29) +{ + unsigned long *sp = (unsigned long *)(reg29 & ~3); + unsigned long addr; + + printk("Call Trace:"); +#ifdef CONFIG_KALLSYMS + printk("\n"); +#endif + while (!kstack_end(sp)) { + unsigned long __user *p = + (unsigned long __user *)(unsigned long)sp++; + if (__get_user(addr, p)) { + printk(" (Bad stack address)"); + break; + } + if (__kernel_text_address(addr)) + print_ip_sym(addr); + } + printk("\n"); +} + +#ifdef CONFIG_KALLSYMS +int raw_show_trace; +static int __init set_raw_show_trace(char *str) +{ + raw_show_trace = 1; + return 1; +} +__setup("raw_show_trace", set_raw_show_trace); +#endif + +static void show_backtrace(struct task_struct *task, const struct pt_regs *regs) +{ + unsigned long sp = regs->regs[29]; + unsigned long ra = regs->regs[31]; + unsigned long pc = regs->cp0_epc; + + if (raw_show_trace || !__kernel_text_address(pc)) { + show_raw_backtrace(sp); + return; + } + printk("Call Trace:\n"); + do { + print_ip_sym(pc); + pc = unwind_stack(task, &sp, pc, &ra); + } while (pc); + printk("\n"); +} + +/* + * This routine abuses get_user()/put_user() to reference pointers + * with at least a bit of error checking ... + */ +static void show_stacktrace(struct task_struct *task, const struct pt_regs *regs) +{ + const int field = 2 * sizeof(unsigned long); + long stackdata; + int i; + unsigned long __user *sp = (unsigned long __user *)regs->regs[29]; + + printk("Stack :"); + i = 0; + while ((unsigned long) sp & (PAGE_SIZE - 1)) { + if (i && ((i % (64 / field)) == 0)) + printk("\n "); + if (i > 39) { + printk(" ..."); + break; + } + + if (__get_user(stackdata, sp++)) { + printk(" (Bad stack address)"); + break; + } + + printk(" %0*lx", field, stackdata); + i++; + } + printk("\n"); + show_backtrace(task, regs); +} + +void show_stack(struct task_struct *task, unsigned long *sp) +{ + struct pt_regs regs; + if (sp) { + regs.regs[29] = (unsigned long)sp; + regs.regs[31] = 0; + regs.cp0_epc = 0; + } else { + if (task && task != current) { + regs.regs[29] = task->thread.reg29; + regs.regs[31] = 0; + regs.cp0_epc = task->thread.reg31; + } else { + prepare_frametrace(®s); + } + } + show_stacktrace(task, ®s); +} + +void dump_uptime(void) +{ + struct timespec uptime; + struct timespec idle; + int i; + cputime_t idletime = cputime_zero; + + for_each_possible_cpu(i) + idletime = cputime64_add(idletime, kstat_cpu(i).cpustat.idle); + + do_posix_clock_monotonic_gettime(&uptime); + monotonic_to_bootbased(&uptime); + cputime_to_timespec(idletime, &idle); + printk("uptime: %lu.%02lu %lu.%02lu\n", + (unsigned long) uptime.tv_sec, + (uptime.tv_nsec / (NSEC_PER_SEC / 100)), + (unsigned long) idle.tv_sec, + (idle.tv_nsec / (NSEC_PER_SEC / 100))); +} + +/* + * The architecture-independent dump_stack generator + */ +void dump_stack(void) +{ + struct pt_regs regs; + + dump_uptime(); + prepare_frametrace(®s); + show_backtrace(current, ®s); +} + +EXPORT_SYMBOL(dump_stack); + +static void show_code(unsigned int __user *pc) +{ + long i; + unsigned short __user *pc16 = NULL; + + printk("\nCode:"); + + if ((unsigned long)pc & 1) + pc16 = (unsigned short __user *)((unsigned long)pc & ~1); + for(i = -3 ; i < 6 ; i++) { + unsigned int insn; + if (pc16 ? __get_user(insn, pc16 + i) : __get_user(insn, pc + i)) { + printk(" (Bad address in epc)\n"); + break; + } + printk("%c%0*x%c", (i?' ':'<'), pc16 ? 4 : 8, insn, (i?' ':'>')); + } +} + +static void __show_regs(const struct pt_regs *regs) +{ + const int field = 2 * sizeof(unsigned long); + unsigned int cause = regs->cp0_cause; + int i; + + printk("Cpu %d\n", smp_processor_id()); + + /* + * Saved main processor registers + */ + for (i = 0; i < 32; ) { + if ((i % 4) == 0) + printk("$%2d :", i); + if (i == 0) + printk(" %0*lx", field, 0UL); + else if (i == 26 || i == 27) + printk(" %*s", field, ""); + else + printk(" %0*lx", field, regs->regs[i]); + + i++; + if ((i % 4) == 0) + printk("\n"); + } + + printk("Hi : %0*lx\n", field, regs->hi); + printk("Lo : %0*lx\n", field, regs->lo); + + /* + * Saved cp0 registers + */ + printk("epc : %0*lx %pS\n", field, regs->cp0_epc, + (void *) regs->cp0_epc); + printk(" %s\n", print_tainted()); + printk("ra : %0*lx %pS\n", field, regs->regs[31], + (void *) regs->regs[31]); + + printk("Status: %08x ", (uint32_t) regs->cp0_status); + + if (regs->cp0_status & ST0_KUO) printk("KUo "); + if (regs->cp0_status & ST0_IEO) printk("IEo "); + if (regs->cp0_status & ST0_KUP) printk("KUp "); + if (regs->cp0_status & ST0_IEP) printk("IEp "); + if (regs->cp0_status & ST0_KUC) printk("KUc "); + if (regs->cp0_status & ST0_IEC) printk("IEc "); + + printk("\n"); + + printk("Cause : %08x\n", cause); + + cause = (cause & CAUSEF_EXCCODE) >> CAUSEB_EXCCODE; + if (1 <= cause && cause <= 5) + printk("BadVA : %0*lx\n", field, regs->cp0_badvaddr); + + printk("PrId : %08x (%s)\n", read_c0_prid(), cpu_name_string()); +} + +/* + * FIXME: really the generic show_regs should take a const pointer argument. + */ +void show_regs(struct pt_regs *regs) +{ + __show_regs((struct pt_regs *)regs); +} + +void show_registers(const struct pt_regs *regs) +{ + const int field = 2 * sizeof(unsigned long); + + __show_regs(regs); + print_modules(); + printk("Process %s (pid: %d, threadinfo=%p, task=%p, tls=%0*lx)\n", + current->comm, current->pid, current_thread_info(), current, + field, current_thread_info()->tp_value); + + show_stacktrace(current, regs); + show_code((unsigned int __user *) regs->cp0_epc); + printk("\n"); +} + +static DEFINE_SPINLOCK(die_lock); + +void __noreturn die(const char * str, const struct pt_regs * regs) +{ + static int die_counter; + + console_verbose(); + spin_lock_irq(&die_lock); + bust_spinlocks(1); + printk("%s[#%d]:\n", str, ++die_counter); + show_registers(regs); + add_taint(TAINT_DIE); +#if defined(CONFIG_RTL_WTDOG) + { extern int is_fault; is_fault=1; } // set kernel fault flag +#endif + spin_unlock_irq(&die_lock); + + if (in_interrupt()) + panic("Fatal exception in interrupt"); + + if (panic_on_oops) { + printk(KERN_EMERG "Fatal exception: panic in 5 seconds\n"); + ssleep(5); + panic("Fatal exception"); + } + + do_exit(SIGSEGV); +} + + +#define OPCODE 0xfc000000 +#define BASE 0x03e00000 +#define RT 0x001f0000 +#define OFFSET 0x0000ffff +#define LL 0xc0000000 +#define SC 0xe0000000 +#define SPEC0 0x00000000 +#define SPEC3 0x7c000000 +#define RD 0x0000f800 +#define FUNC 0x0000003f +#define SYNC 0x0000000f +#define RDHWR 0x0000003b + +/* ll/sc emulation */ +#ifndef CONFIG_CPU_HAS_LLSC + +/* The ll_bit is cleared by r*_switch.S */ + +unsigned long ll_bit; + +static struct task_struct *ll_task = NULL; + +static inline int simulate_ll(struct pt_regs *regs, unsigned int opcode) +{ + unsigned long value, __user *vaddr; + long offset; + + /* + * analyse the ll instruction that just caused a ri exception + * and put the referenced address to addr. + */ + + /* sign extend offset */ + offset = opcode & OFFSET; + offset <<= 16; + offset >>= 16; + + vaddr = (unsigned long __user *) + ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); + + if ((unsigned long)vaddr & 3) + return SIGBUS; + if (get_user(value, vaddr)) + return SIGSEGV; + + preempt_disable(); + + if (ll_task == NULL || ll_task == current) { + ll_bit = 1; + } else { + ll_bit = 0; + } + ll_task = current; + + preempt_enable(); + + regs->regs[(opcode & RT) >> 16] = value; + + return 0; +} + +static inline int simulate_sc(struct pt_regs *regs, unsigned int opcode) +{ + unsigned long __user *vaddr; + unsigned long reg; + long offset; + + /* + * analyse the sc instruction that just caused a ri exception + * and put the referenced address to addr. + */ + + /* sign extend offset */ + offset = opcode & OFFSET; + offset <<= 16; + offset >>= 16; + + vaddr = (unsigned long __user *) + ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); + reg = (opcode & RT) >> 16; + + if ((unsigned long)vaddr & 3) + return SIGBUS; + + preempt_disable(); + + if (ll_bit == 0 || ll_task != current) { + regs->regs[reg] = 0; + preempt_enable(); + return 0; + } + + preempt_enable(); + + if (put_user(regs->regs[reg], vaddr)) + return SIGSEGV; + + regs->regs[reg] = 1; + + return 0; +} + +/* + * ll uses the opcode of lwc0 and sc uses the opcode of swc0. That is both + * opcodes are supposed to result in coprocessor unusable exceptions if + * executed on ll/sc-less processors. That's the theory. In practice a + * few processors such as NEC's VR4100 throw reserved instruction exceptions + * instead, so we're doing the emulation thing in both exception handlers. + */ +static int simulate_llsc(struct pt_regs *regs, unsigned int opcode) +{ + if ((opcode & OPCODE) == LL) + return simulate_ll(regs, opcode); + if ((opcode & OPCODE) == SC) + return simulate_sc(regs, opcode); + + return -1; /* Must be something else ... */ +} +#endif + +#ifndef CONFIG_CPU_HAS_SYNC +static int simulate_sync(struct pt_regs *regs, unsigned int opcode) +{ + if ((opcode & OPCODE) == SPEC0 && (opcode & FUNC) == SYNC) + return 0; + + return -1; /* Must be something else ... */ +} +#endif + +asmlinkage void do_ov(struct pt_regs *regs) +{ + siginfo_t info; + + die_if_kernel("Integer overflow", regs); + + info.si_code = FPE_INTOVF; + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_addr = (void __user *) regs->cp0_epc; + force_sig_info(SIGFPE, &info, current); +} + +static void do_trap_or_bp(struct pt_regs *regs, unsigned int code, const char *str) +{ + siginfo_t info; + char b[40]; + + if (notify_die(DIE_TRAP, str, regs, code, 0, 0) == NOTIFY_STOP) + return; + + /* + * A short test says that IRIX 5.3 sends SIGTRAP for all trap + * insns, even for trap and break codes that indicate arithmetic + * failures. Weird ... + * But should we continue the brokenness??? --macro + */ + switch (code) { + case BRK_OVERFLOW: + case BRK_DIVZERO: + scnprintf(b, sizeof(b), "%s instruction in kernel code", str); + die_if_kernel(b, regs); + if (code == BRK_DIVZERO) + info.si_code = FPE_INTDIV; + else + info.si_code = FPE_INTOVF; + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_addr = (void __user *) regs->cp0_epc; + force_sig_info(SIGFPE, &info, current); + break; + case BRK_BUG: + die_if_kernel("Kernel bug detected", regs); + force_sig(SIGTRAP, current); + break; + default: + scnprintf(b, sizeof(b), "%s instruction in kernel code", str); + die_if_kernel(b, regs); + force_sig(SIGTRAP, current); + } +} + +asmlinkage void do_bp(struct pt_regs *regs) +{ + unsigned int opcode, bcode; + + if (__get_user(opcode, (unsigned int __user *) exception_epc(regs))) + goto out_sigsegv; + + /* + * There is the ancient bug in the MIPS assemblers that the break + * code starts left to bit 16 instead to bit 6 in the opcode. + * Gas is bug-compatible, but not always, grrr... + * We handle both cases with a simple heuristics. --macro + */ + bcode = ((opcode >> 6) & ((1 << 20) - 1)); + if (bcode >= (1 << 10)) + bcode >>= 10; + + do_trap_or_bp(regs, bcode, "Break"); +#if defined(CONFIG_RTL_WTDOG) + if (bcode == 7){ // divided by zero + die("Oops", regs); + { extern int is_fault; is_fault=1;} // set kernel fault flag + } +#endif + return; + +out_sigsegv: + force_sig(SIGSEGV, current); +} + +asmlinkage void do_ri(struct pt_regs *regs) +{ + unsigned int __user *epc = (unsigned int __user *)exception_epc(regs); + unsigned long old_epc = regs->cp0_epc; + unsigned int opcode = 0; + int status = -1; + + if (notify_die(DIE_RI, "RI Fault", regs, SIGSEGV, 0, 0) + == NOTIFY_STOP) + return; + + die_if_kernel("Reserved instruction in kernel code", regs); + + if (unlikely(compute_return_epc(regs) < 0)) + return; + + if (unlikely(get_user(opcode, epc) < 0)) + status = SIGSEGV; + +#ifndef CONFIG_CPU_HAS_LLSC + if (status < 0) + status = simulate_llsc(regs, opcode); +#endif + +#if 0 + if (status < 0) + status = simulate_rdhwr(regs, opcode); +#endif + +#ifndef CONFIG_CPU_HAS_SYNC + if (status < 0) + status = simulate_sync(regs, opcode); +#endif + + if (status < 0) + status = SIGILL; + + if (unlikely(status > 0)) { + regs->cp0_epc = old_epc; /* Undo skip-over. */ + force_sig(status, current); + } +} + +asmlinkage void do_cpu(struct pt_regs *regs) +{ + unsigned int __user *epc; + unsigned long old_epc; + unsigned int opcode; + unsigned int cpid; + int status; + unsigned long __maybe_unused flags; + + die_if_kernel("do_cpu invoked from kernel context!", regs); + + cpid = (regs->cp0_cause >> CAUSEB_CE) & 3; + if (cpid == 0) + { + epc = (unsigned int __user *)exception_epc(regs); + old_epc = regs->cp0_epc; + opcode = 0; + status = -1; + + if (unlikely(compute_return_epc(regs) < 0)) + return; + + if (unlikely(get_user(opcode, epc) < 0)) + status = SIGSEGV; + +#ifndef CONFIG_CPU_HAS_LLSC + if (status < 0) + status = simulate_llsc(regs, opcode); +#endif + +#if 0 + if (status < 0) + status = simulate_rdhwr(regs, opcode); +#endif + + if (status < 0) + status = SIGILL; + + if (unlikely(status > 0)) { + regs->cp0_epc = old_epc; /* Undo skip-over. */ + force_sig(status, current); + } + + return; + } + + force_sig(SIGILL, current); +} + +asmlinkage void do_reserved(struct pt_regs *regs) +{ + /* + * Game over - no way to handle this if it ever occurs. Most probably + * caused by a new unknown cpu type or after another deadly + * hard/software error. + */ + show_regs(regs); + panic("Caught reserved exception %ld - should not happen.", + (regs->cp0_cause & 0x7f) >> 2); +} + +unsigned long ebase; +unsigned long exception_handlers[32]; + +/* + * As a side effect of the way this is implemented we're limited + * to interrupt handlers in the address range from + * KSEG0 <= x < KSEG0 + 256mb on the Nevada. Oh well ... + */ +void *set_except_vector(int n, void *addr) +{ + unsigned long handler = (unsigned long) addr; + unsigned long old_handler = exception_handlers[n]; + + exception_handlers[n] = handler; + return (void *)old_handler; +} + +extern void cpu_cache_init(void); +extern void tlb_init(void); +extern void flush_tlb_handlers(void); + +void __cpuinit per_cpu_trap_init(void) +{ + unsigned int cpu = smp_processor_id(); + unsigned int status_set = ST0_CU0; + + /* clear CU* and BEV, enable CU0 */ + change_c0_status(ST0_CU|ST0_BEV, status_set); + + cpu_data[cpu].asid_cache = ASID_FIRST_VERSION; + TLBMISS_HANDLER_SETUP(); + + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + BUG_ON(current->mm); + enter_lazy_tlb(&init_mm, current); + + cpu_cache_init(); + tlb_init(); +} + +#define RLX_TRAP_VEC_BASE 0x80000080 +#define RLX_TRAP_VEC_SIZE 0x80 + +void __init trap_init(void) +{ + extern char rlx_trap_dispatch; + unsigned long i; + +#if defined(CONFIG_KGDB) + if (kgdb_early_setup) + return; /* Already done */ +#endif + + ebase = CAC_BASE; + + per_cpu_trap_init(); + + /* + * Setup default vectors + */ + for (i = 0; i <= 31; i++) + set_except_vector(i, handle_reserved); + + /* + * Initialise interrupt handlers + */ + set_except_vector(0, rlx_irq_dispatch); + set_except_vector(1, handle_tlbm); + set_except_vector(2, handle_tlbl); + set_except_vector(3, handle_tlbs); + + set_except_vector(4, handle_adel); + set_except_vector(5, handle_ades); + + set_except_vector(8, handle_sys); + set_except_vector(9, handle_bp); + set_except_vector(10, handle_ri); + set_except_vector(11, handle_cpu); + set_except_vector(12, handle_ov); + + memcpy ((void *) (RLX_TRAP_VEC_BASE), &rlx_trap_dispatch, RLX_TRAP_VEC_SIZE); + + flush_icache_range (RLX_TRAP_VEC_BASE, RLX_TRAP_VEC_BASE+RLX_TRAP_VEC_SIZE); + flush_tlb_handlers (); +} |