| /* |
| * Meta exception handling. |
| * |
| * Copyright (C) 2005,2006,2007,2008,2009,2012 Imagination Technologies Ltd. |
| * |
| * 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/export.h> |
| #include <linux/sched.h> |
| #include <linux/sched/debug.h> |
| #include <linux/sched/task.h> |
| #include <linux/sched/task_stack.h> |
| #include <linux/signal.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/types.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/preempt.h> |
| #include <linux/ptrace.h> |
| #include <linux/module.h> |
| #include <linux/kallsyms.h> |
| #include <linux/kdebug.h> |
| #include <linux/kexec.h> |
| #include <linux/unistd.h> |
| #include <linux/smp.h> |
| #include <linux/slab.h> |
| #include <linux/syscalls.h> |
| |
| #include <asm/bug.h> |
| #include <asm/core_reg.h> |
| #include <asm/irqflags.h> |
| #include <asm/siginfo.h> |
| #include <asm/traps.h> |
| #include <asm/hwthread.h> |
| #include <asm/setup.h> |
| #include <asm/switch.h> |
| #include <asm/user_gateway.h> |
| #include <asm/syscall.h> |
| #include <asm/syscalls.h> |
| |
| /* Passing syscall arguments as long long is quicker. */ |
| typedef unsigned int (*LPSYSCALL) (unsigned long long, |
| unsigned long long, |
| unsigned long long); |
| |
| /* |
| * Users of LNKSET should compare the bus error bits obtained from DEFR |
| * against TXDEFR_LNKSET_SUCCESS only as the failure code will vary between |
| * different cores revisions. |
| */ |
| #define TXDEFR_LNKSET_SUCCESS 0x02000000 |
| #define TXDEFR_LNKSET_FAILURE 0x04000000 |
| |
| /* |
| * Our global TBI handle. Initialised from setup.c/setup_arch. |
| */ |
| DECLARE_PER_CPU(PTBI, pTBI); |
| |
| #ifdef CONFIG_SMP |
| static DEFINE_PER_CPU(unsigned int, trigger_mask); |
| #else |
| unsigned int global_trigger_mask; |
| EXPORT_SYMBOL(global_trigger_mask); |
| #endif |
| |
| unsigned long per_cpu__stack_save[NR_CPUS]; |
| |
| static const char * const trap_names[] = { |
| [TBIXXF_SIGNUM_IIF] = "Illegal instruction fault", |
| [TBIXXF_SIGNUM_PGF] = "Privilege violation", |
| [TBIXXF_SIGNUM_DHF] = "Unaligned data access fault", |
| [TBIXXF_SIGNUM_IGF] = "Code fetch general read failure", |
| [TBIXXF_SIGNUM_DGF] = "Data access general read/write fault", |
| [TBIXXF_SIGNUM_IPF] = "Code fetch page fault", |
| [TBIXXF_SIGNUM_DPF] = "Data access page fault", |
| [TBIXXF_SIGNUM_IHF] = "Instruction breakpoint", |
| [TBIXXF_SIGNUM_DWF] = "Read-only data access fault", |
| }; |
| |
| const char *trap_name(int trapno) |
| { |
| if (trapno >= 0 && trapno < ARRAY_SIZE(trap_names) |
| && trap_names[trapno]) |
| return trap_names[trapno]; |
| return "Unknown fault"; |
| } |
| |
| static DEFINE_SPINLOCK(die_lock); |
| |
| void __noreturn die(const char *str, struct pt_regs *regs, |
| long err, unsigned long addr) |
| { |
| static int die_counter; |
| |
| oops_enter(); |
| |
| spin_lock_irq(&die_lock); |
| console_verbose(); |
| bust_spinlocks(1); |
| pr_err("%s: err %04lx (%s) addr %08lx [#%d]\n", str, err & 0xffff, |
| trap_name(err & 0xffff), addr, ++die_counter); |
| |
| print_modules(); |
| show_regs(regs); |
| |
| pr_err("Process: %s (pid: %d, stack limit = %p)\n", current->comm, |
| task_pid_nr(current), task_stack_page(current) + THREAD_SIZE); |
| |
| bust_spinlocks(0); |
| add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE); |
| if (kexec_should_crash(current)) |
| crash_kexec(regs); |
| |
| if (in_interrupt()) |
| panic("Fatal exception in interrupt"); |
| |
| if (panic_on_oops) |
| panic("Fatal exception"); |
| |
| spin_unlock_irq(&die_lock); |
| oops_exit(); |
| do_exit(SIGSEGV); |
| } |
| |
| #ifdef CONFIG_METAG_DSP |
| /* |
| * The ECH encoding specifies the size of a DSPRAM as, |
| * |
| * "slots" / 4 |
| * |
| * A "slot" is the size of two DSPRAM bank entries; an entry from |
| * DSPRAM bank A and an entry from DSPRAM bank B. One DSPRAM bank |
| * entry is 4 bytes. |
| */ |
| #define SLOT_SZ 8 |
| static inline unsigned int decode_dspram_size(unsigned int size) |
| { |
| unsigned int _sz = size & 0x7f; |
| |
| return _sz * SLOT_SZ * 4; |
| } |
| |
| static void dspram_save(struct meta_ext_context *dsp_ctx, |
| unsigned int ramA_sz, unsigned int ramB_sz) |
| { |
| unsigned int ram_sz[2]; |
| int i; |
| |
| ram_sz[0] = ramA_sz; |
| ram_sz[1] = ramB_sz; |
| |
| for (i = 0; i < 2; i++) { |
| if (ram_sz[i] != 0) { |
| unsigned int sz; |
| |
| if (i == 0) |
| sz = decode_dspram_size(ram_sz[i] >> 8); |
| else |
| sz = decode_dspram_size(ram_sz[i]); |
| |
| if (dsp_ctx->ram[i] == NULL) { |
| dsp_ctx->ram[i] = kmalloc(sz, GFP_KERNEL); |
| |
| if (dsp_ctx->ram[i] == NULL) |
| panic("couldn't save DSP context"); |
| } else { |
| if (ram_sz[i] > dsp_ctx->ram_sz[i]) { |
| kfree(dsp_ctx->ram[i]); |
| |
| dsp_ctx->ram[i] = kmalloc(sz, |
| GFP_KERNEL); |
| |
| if (dsp_ctx->ram[i] == NULL) |
| panic("couldn't save DSP context"); |
| } |
| } |
| |
| if (i == 0) |
| __TBIDspramSaveA(ram_sz[i], dsp_ctx->ram[i]); |
| else |
| __TBIDspramSaveB(ram_sz[i], dsp_ctx->ram[i]); |
| |
| dsp_ctx->ram_sz[i] = ram_sz[i]; |
| } |
| } |
| } |
| #endif /* CONFIG_METAG_DSP */ |
| |
| /* |
| * Allow interrupts to be nested and save any "extended" register |
| * context state, e.g. DSP regs and RAMs. |
| */ |
| static void nest_interrupts(TBIRES State, unsigned long mask) |
| { |
| #ifdef CONFIG_METAG_DSP |
| struct meta_ext_context *dsp_ctx; |
| unsigned int D0_8; |
| |
| /* |
| * D0.8 may contain an ECH encoding. The upper 16 bits |
| * tell us what DSP resources the current process is |
| * using. OR the bits into the SaveMask so that |
| * __TBINestInts() knows what resources to save as |
| * part of this context. |
| * |
| * Don't save the context if we're nesting interrupts in the |
| * kernel because the kernel doesn't use DSP hardware. |
| */ |
| D0_8 = __core_reg_get(D0.8); |
| |
| if (D0_8 && (State.Sig.SaveMask & TBICTX_PRIV_BIT)) { |
| State.Sig.SaveMask |= (D0_8 >> 16); |
| |
| dsp_ctx = current->thread.dsp_context; |
| if (dsp_ctx == NULL) { |
| dsp_ctx = kzalloc(sizeof(*dsp_ctx), GFP_KERNEL); |
| if (dsp_ctx == NULL) |
| panic("couldn't save DSP context: ENOMEM"); |
| |
| current->thread.dsp_context = dsp_ctx; |
| } |
| |
| current->thread.user_flags |= (D0_8 & 0xffff0000); |
| __TBINestInts(State, &dsp_ctx->regs, mask); |
| dspram_save(dsp_ctx, D0_8 & 0x7f00, D0_8 & 0x007f); |
| } else |
| __TBINestInts(State, NULL, mask); |
| #else |
| __TBINestInts(State, NULL, mask); |
| #endif |
| } |
| |
| void head_end(TBIRES State, unsigned long mask) |
| { |
| unsigned int savemask = (unsigned short)State.Sig.SaveMask; |
| unsigned int ctx_savemask = (unsigned short)State.Sig.pCtx->SaveMask; |
| |
| if (savemask & TBICTX_PRIV_BIT) { |
| ctx_savemask |= TBICTX_PRIV_BIT; |
| current->thread.user_flags = savemask; |
| } |
| |
| /* Always undo the sleep bit */ |
| ctx_savemask &= ~TBICTX_WAIT_BIT; |
| |
| /* Always save the catch buffer and RD pipe if they are dirty */ |
| savemask |= TBICTX_XCBF_BIT; |
| |
| /* Only save the catch and RD if we have not already done so. |
| * Note - the RD bits are in the pCtx only, and not in the |
| * State.SaveMask. |
| */ |
| if ((savemask & TBICTX_CBUF_BIT) || |
| (ctx_savemask & TBICTX_CBRP_BIT)) { |
| /* Have we already saved the buffers though? |
| * - See TestTrack 5071 */ |
| if (ctx_savemask & TBICTX_XCBF_BIT) { |
| /* Strip off the bits so the call to __TBINestInts |
| * won't save the buffers again. */ |
| savemask &= ~TBICTX_CBUF_BIT; |
| ctx_savemask &= ~TBICTX_CBRP_BIT; |
| } |
| } |
| |
| #ifdef CONFIG_METAG_META21 |
| { |
| unsigned int depth, txdefr; |
| |
| /* |
| * Save TXDEFR state. |
| * |
| * The process may have been interrupted after a LNKSET, but |
| * before it could read the DEFR state, so we mustn't lose that |
| * state or it could end up retrying an atomic operation that |
| * succeeded. |
| * |
| * All interrupts are disabled at this point so we |
| * don't need to perform any locking. We must do this |
| * dance before we use LNKGET or LNKSET. |
| */ |
| BUG_ON(current->thread.int_depth > HARDIRQ_BITS); |
| |
| depth = current->thread.int_depth++; |
| |
| txdefr = __core_reg_get(TXDEFR); |
| |
| txdefr &= TXDEFR_BUS_STATE_BITS; |
| if (txdefr & TXDEFR_LNKSET_SUCCESS) |
| current->thread.txdefr_failure &= ~(1 << depth); |
| else |
| current->thread.txdefr_failure |= (1 << depth); |
| } |
| #endif |
| |
| State.Sig.SaveMask = savemask; |
| State.Sig.pCtx->SaveMask = ctx_savemask; |
| |
| nest_interrupts(State, mask); |
| |
| #ifdef CONFIG_METAG_POISON_CATCH_BUFFERS |
| /* Poison the catch registers. This shows up any mistakes we have |
| * made in their handling MUCH quicker. |
| */ |
| __core_reg_set(TXCATCH0, 0x87650021); |
| __core_reg_set(TXCATCH1, 0x87654322); |
| __core_reg_set(TXCATCH2, 0x87654323); |
| __core_reg_set(TXCATCH3, 0x87654324); |
| #endif /* CONFIG_METAG_POISON_CATCH_BUFFERS */ |
| } |
| |
| TBIRES tail_end_sys(TBIRES State, int syscall, int *restart) |
| { |
| struct pt_regs *regs = (struct pt_regs *)State.Sig.pCtx; |
| unsigned long flags; |
| |
| local_irq_disable(); |
| |
| if (user_mode(regs)) { |
| flags = current_thread_info()->flags; |
| if (flags & _TIF_WORK_MASK && |
| do_work_pending(regs, flags, syscall)) { |
| *restart = 1; |
| return State; |
| } |
| |
| #ifdef CONFIG_METAG_FPU |
| if (current->thread.fpu_context && |
| current->thread.fpu_context->needs_restore) { |
| __TBICtxFPURestore(State, current->thread.fpu_context); |
| /* |
| * Clearing this bit ensures the FP unit is not made |
| * active again unless it is used. |
| */ |
| State.Sig.SaveMask &= ~TBICTX_FPAC_BIT; |
| current->thread.fpu_context->needs_restore = false; |
| } |
| State.Sig.TrigMask |= TBI_TRIG_BIT(TBID_SIGNUM_DFR); |
| #endif |
| } |
| |
| /* TBI will turn interrupts back on at some point. */ |
| if (!irqs_disabled_flags((unsigned long)State.Sig.TrigMask)) |
| trace_hardirqs_on(); |
| |
| #ifdef CONFIG_METAG_DSP |
| /* |
| * If we previously saved an extended context then restore it |
| * now. Otherwise, clear D0.8 because this process is not |
| * using DSP hardware. |
| */ |
| if (State.Sig.pCtx->SaveMask & TBICTX_XEXT_BIT) { |
| unsigned int D0_8; |
| struct meta_ext_context *dsp_ctx = current->thread.dsp_context; |
| |
| /* Make sure we're going to return to userland. */ |
| BUG_ON(current->thread.int_depth != 1); |
| |
| if (dsp_ctx->ram_sz[0] > 0) |
| __TBIDspramRestoreA(dsp_ctx->ram_sz[0], |
| dsp_ctx->ram[0]); |
| if (dsp_ctx->ram_sz[1] > 0) |
| __TBIDspramRestoreB(dsp_ctx->ram_sz[1], |
| dsp_ctx->ram[1]); |
| |
| State.Sig.SaveMask |= State.Sig.pCtx->SaveMask; |
| __TBICtxRestore(State, current->thread.dsp_context); |
| D0_8 = __core_reg_get(D0.8); |
| D0_8 |= current->thread.user_flags & 0xffff0000; |
| D0_8 |= (dsp_ctx->ram_sz[1] | dsp_ctx->ram_sz[0]) & 0xffff; |
| __core_reg_set(D0.8, D0_8); |
| } else |
| __core_reg_set(D0.8, 0); |
| #endif /* CONFIG_METAG_DSP */ |
| |
| #ifdef CONFIG_METAG_META21 |
| { |
| unsigned int depth, txdefr; |
| |
| /* |
| * If there hasn't been a LNKSET since the last LNKGET then the |
| * link flag will be set, causing the next LNKSET to succeed if |
| * the addresses match. The two LNK operations may not be a pair |
| * (e.g. see atomic_read()), so the LNKSET should fail. |
| * We use a conditional-never LNKSET to clear the link flag |
| * without side effects. |
| */ |
| asm volatile("LNKSETDNV [D0Re0],D0Re0"); |
| |
| depth = --current->thread.int_depth; |
| |
| BUG_ON(user_mode(regs) && depth); |
| |
| txdefr = __core_reg_get(TXDEFR); |
| |
| txdefr &= ~TXDEFR_BUS_STATE_BITS; |
| |
| /* Do we need to restore a failure code into TXDEFR? */ |
| if (current->thread.txdefr_failure & (1 << depth)) |
| txdefr |= (TXDEFR_LNKSET_FAILURE | TXDEFR_BUS_TRIG_BIT); |
| else |
| txdefr |= (TXDEFR_LNKSET_SUCCESS | TXDEFR_BUS_TRIG_BIT); |
| |
| __core_reg_set(TXDEFR, txdefr); |
| } |
| #endif |
| return State; |
| } |
| |
| #ifdef CONFIG_SMP |
| /* |
| * If we took an interrupt in the middle of __kuser_get_tls then we need |
| * to rewind the PC to the start of the function in case the process |
| * gets migrated to another thread (SMP only) and it reads the wrong tls |
| * data. |
| */ |
| static inline void _restart_critical_section(TBIRES State) |
| { |
| unsigned long get_tls_start; |
| unsigned long get_tls_end; |
| |
| get_tls_start = (unsigned long)__kuser_get_tls - |
| (unsigned long)&__user_gateway_start; |
| |
| get_tls_start += USER_GATEWAY_PAGE; |
| |
| get_tls_end = (unsigned long)__kuser_get_tls_end - |
| (unsigned long)&__user_gateway_start; |
| |
| get_tls_end += USER_GATEWAY_PAGE; |
| |
| if ((State.Sig.pCtx->CurrPC >= get_tls_start) && |
| (State.Sig.pCtx->CurrPC < get_tls_end)) |
| State.Sig.pCtx->CurrPC = get_tls_start; |
| } |
| #else |
| /* |
| * If we took an interrupt in the middle of |
| * __kuser_cmpxchg then we need to rewind the PC to the |
| * start of the function. |
| */ |
| static inline void _restart_critical_section(TBIRES State) |
| { |
| unsigned long cmpxchg_start; |
| unsigned long cmpxchg_end; |
| |
| cmpxchg_start = (unsigned long)__kuser_cmpxchg - |
| (unsigned long)&__user_gateway_start; |
| |
| cmpxchg_start += USER_GATEWAY_PAGE; |
| |
| cmpxchg_end = (unsigned long)__kuser_cmpxchg_end - |
| (unsigned long)&__user_gateway_start; |
| |
| cmpxchg_end += USER_GATEWAY_PAGE; |
| |
| if ((State.Sig.pCtx->CurrPC >= cmpxchg_start) && |
| (State.Sig.pCtx->CurrPC < cmpxchg_end)) |
| State.Sig.pCtx->CurrPC = cmpxchg_start; |
| } |
| #endif |
| |
| /* Used by kick_handler() */ |
| void restart_critical_section(TBIRES State) |
| { |
| _restart_critical_section(State); |
| } |
| |
| TBIRES trigger_handler(TBIRES State, int SigNum, int Triggers, int Inst, |
| PTBI pTBI) |
| { |
| head_end(State, ~INTS_OFF_MASK); |
| |
| /* If we interrupted user code handle any critical sections. */ |
| if (State.Sig.SaveMask & TBICTX_PRIV_BIT) |
| _restart_critical_section(State); |
| |
| trace_hardirqs_off(); |
| |
| do_IRQ(SigNum, (struct pt_regs *)State.Sig.pCtx); |
| |
| return tail_end(State); |
| } |
| |
| static unsigned int load_fault(PTBICTXEXTCB0 pbuf) |
| { |
| return pbuf->CBFlags & TXCATCH0_READ_BIT; |
| } |
| |
| static unsigned long fault_address(PTBICTXEXTCB0 pbuf) |
| { |
| return pbuf->CBAddr; |
| } |
| |
| static void unhandled_fault(struct pt_regs *regs, unsigned long addr, |
| int signo, int code, int trapno) |
| { |
| if (user_mode(regs)) { |
| siginfo_t info; |
| |
| if (show_unhandled_signals && unhandled_signal(current, signo) |
| && printk_ratelimit()) { |
| |
| pr_info("pid %d unhandled fault: pc 0x%08x, addr 0x%08lx, trap %d (%s)\n", |
| current->pid, regs->ctx.CurrPC, addr, |
| trapno, trap_name(trapno)); |
| print_vma_addr(" in ", regs->ctx.CurrPC); |
| print_vma_addr(" rtp in ", regs->ctx.DX[4].U1); |
| printk("\n"); |
| show_regs(regs); |
| } |
| |
| info.si_signo = signo; |
| info.si_errno = 0; |
| info.si_code = code; |
| info.si_addr = (__force void __user *)addr; |
| info.si_trapno = trapno; |
| force_sig_info(signo, &info, current); |
| } else { |
| die("Oops", regs, trapno, addr); |
| } |
| } |
| |
| static int handle_data_fault(PTBICTXEXTCB0 pcbuf, struct pt_regs *regs, |
| unsigned int data_address, int trapno) |
| { |
| int ret; |
| |
| ret = do_page_fault(regs, data_address, !load_fault(pcbuf), trapno); |
| |
| return ret; |
| } |
| |
| static unsigned long get_inst_fault_address(struct pt_regs *regs) |
| { |
| return regs->ctx.CurrPC; |
| } |
| |
| TBIRES fault_handler(TBIRES State, int SigNum, int Triggers, |
| int Inst, PTBI pTBI) |
| { |
| struct pt_regs *regs = (struct pt_regs *)State.Sig.pCtx; |
| PTBICTXEXTCB0 pcbuf = (PTBICTXEXTCB0)®s->extcb0; |
| unsigned long data_address; |
| |
| head_end(State, ~INTS_OFF_MASK); |
| |
| /* Hardware breakpoint or data watch */ |
| if ((SigNum == TBIXXF_SIGNUM_IHF) || |
| ((SigNum == TBIXXF_SIGNUM_DHF) && |
| (pcbuf[0].CBFlags & (TXCATCH0_WATCH1_BIT | |
| TXCATCH0_WATCH0_BIT)))) { |
| State = __TBIUnExpXXX(State, SigNum, Triggers, Inst, |
| pTBI); |
| return tail_end(State); |
| } |
| |
| local_irq_enable(); |
| |
| data_address = fault_address(pcbuf); |
| |
| switch (SigNum) { |
| case TBIXXF_SIGNUM_IGF: |
| /* 1st-level entry invalid (instruction fetch) */ |
| case TBIXXF_SIGNUM_IPF: { |
| /* 2nd-level entry invalid (instruction fetch) */ |
| unsigned long addr = get_inst_fault_address(regs); |
| do_page_fault(regs, addr, 0, SigNum); |
| break; |
| } |
| |
| case TBIXXF_SIGNUM_DGF: |
| /* 1st-level entry invalid (data access) */ |
| case TBIXXF_SIGNUM_DPF: |
| /* 2nd-level entry invalid (data access) */ |
| case TBIXXF_SIGNUM_DWF: |
| /* Write to read only page */ |
| handle_data_fault(pcbuf, regs, data_address, SigNum); |
| break; |
| |
| case TBIXXF_SIGNUM_IIF: |
| /* Illegal instruction */ |
| unhandled_fault(regs, regs->ctx.CurrPC, SIGILL, ILL_ILLOPC, |
| SigNum); |
| break; |
| |
| case TBIXXF_SIGNUM_DHF: |
| /* Unaligned access */ |
| unhandled_fault(regs, data_address, SIGBUS, BUS_ADRALN, |
| SigNum); |
| break; |
| case TBIXXF_SIGNUM_PGF: |
| /* Privilege violation */ |
| unhandled_fault(regs, data_address, SIGSEGV, SEGV_ACCERR, |
| SigNum); |
| break; |
| default: |
| BUG(); |
| break; |
| } |
| |
| return tail_end(State); |
| } |
| |
| static bool switch_is_syscall(unsigned int inst) |
| { |
| return inst == __METAG_SW_ENCODING(SYS); |
| } |
| |
| static bool switch_is_legacy_syscall(unsigned int inst) |
| { |
| return inst == __METAG_SW_ENCODING(SYS_LEGACY); |
| } |
| |
| static inline void step_over_switch(struct pt_regs *regs, unsigned int inst) |
| { |
| regs->ctx.CurrPC += 4; |
| } |
| |
| static inline int test_syscall_work(void) |
| { |
| return current_thread_info()->flags & _TIF_WORK_SYSCALL_MASK; |
| } |
| |
| TBIRES switch1_handler(TBIRES State, int SigNum, int Triggers, |
| int Inst, PTBI pTBI) |
| { |
| struct pt_regs *regs = (struct pt_regs *)State.Sig.pCtx; |
| unsigned int sysnumber; |
| unsigned long long a1_a2, a3_a4, a5_a6; |
| LPSYSCALL syscall_entry; |
| int restart; |
| |
| head_end(State, ~INTS_OFF_MASK); |
| |
| /* |
| * If this is not a syscall SWITCH it could be a breakpoint. |
| */ |
| if (!switch_is_syscall(Inst)) { |
| /* |
| * Alert the user if they're trying to use legacy system |
| * calls. This suggests they need to update their C |
| * library and build against up to date kernel headers. |
| */ |
| if (switch_is_legacy_syscall(Inst)) |
| pr_warn_once("WARNING: A legacy syscall was made. Your userland needs updating.\n"); |
| /* |
| * We don't know how to handle the SWITCH and cannot |
| * safely ignore it, so treat all unknown switches |
| * (including breakpoints) as traps. |
| */ |
| force_sig(SIGTRAP, current); |
| return tail_end(State); |
| } |
| |
| local_irq_enable(); |
| |
| restart_syscall: |
| restart = 0; |
| sysnumber = regs->ctx.DX[0].U1; |
| |
| if (test_syscall_work()) |
| sysnumber = syscall_trace_enter(regs); |
| |
| /* Skip over the SWITCH instruction - or you just get 'stuck' on it! */ |
| step_over_switch(regs, Inst); |
| |
| if (sysnumber >= __NR_syscalls) { |
| pr_debug("unknown syscall number: %d\n", sysnumber); |
| syscall_entry = (LPSYSCALL) sys_ni_syscall; |
| } else { |
| syscall_entry = (LPSYSCALL) sys_call_table[sysnumber]; |
| } |
| |
| /* Use 64bit loads for speed. */ |
| a5_a6 = *(unsigned long long *)®s->ctx.DX[1]; |
| a3_a4 = *(unsigned long long *)®s->ctx.DX[2]; |
| a1_a2 = *(unsigned long long *)®s->ctx.DX[3]; |
| |
| /* here is the actual call to the syscall handler functions */ |
| regs->ctx.DX[0].U0 = syscall_entry(a1_a2, a3_a4, a5_a6); |
| |
| if (test_syscall_work()) |
| syscall_trace_leave(regs); |
| |
| State = tail_end_sys(State, sysnumber, &restart); |
| /* Handlerless restarts shouldn't go via userland */ |
| if (restart) |
| goto restart_syscall; |
| return State; |
| } |
| |
| TBIRES switchx_handler(TBIRES State, int SigNum, int Triggers, |
| int Inst, PTBI pTBI) |
| { |
| struct pt_regs *regs = (struct pt_regs *)State.Sig.pCtx; |
| |
| /* |
| * This can be caused by any user process simply executing an unusual |
| * SWITCH instruction. If there's no DA, __TBIUnExpXXX will cause the |
| * thread to stop, so signal a SIGTRAP instead. |
| */ |
| head_end(State, ~INTS_OFF_MASK); |
| if (user_mode(regs)) |
| force_sig(SIGTRAP, current); |
| else |
| State = __TBIUnExpXXX(State, SigNum, Triggers, Inst, pTBI); |
| return tail_end(State); |
| } |
| |
| #ifdef CONFIG_METAG_META21 |
| TBIRES fpe_handler(TBIRES State, int SigNum, int Triggers, int Inst, PTBI pTBI) |
| { |
| struct pt_regs *regs = (struct pt_regs *)State.Sig.pCtx; |
| unsigned int error_state = Triggers; |
| siginfo_t info; |
| |
| head_end(State, ~INTS_OFF_MASK); |
| |
| local_irq_enable(); |
| |
| info.si_signo = SIGFPE; |
| |
| if (error_state & TXSTAT_FPE_INVALID_BIT) |
| info.si_code = FPE_FLTINV; |
| else if (error_state & TXSTAT_FPE_DIVBYZERO_BIT) |
| info.si_code = FPE_FLTDIV; |
| else if (error_state & TXSTAT_FPE_OVERFLOW_BIT) |
| info.si_code = FPE_FLTOVF; |
| else if (error_state & TXSTAT_FPE_UNDERFLOW_BIT) |
| info.si_code = FPE_FLTUND; |
| else if (error_state & TXSTAT_FPE_INEXACT_BIT) |
| info.si_code = FPE_FLTRES; |
| else |
| info.si_code = 0; |
| info.si_errno = 0; |
| info.si_addr = (__force void __user *)regs->ctx.CurrPC; |
| force_sig_info(SIGFPE, &info, current); |
| |
| return tail_end(State); |
| } |
| #endif |
| |
| #ifdef CONFIG_METAG_SUSPEND_MEM |
| struct traps_context { |
| PTBIAPIFN fnSigs[TBID_SIGNUM_MAX + 1]; |
| }; |
| |
| static struct traps_context *metag_traps_context; |
| |
| int traps_save_context(void) |
| { |
| unsigned long cpu = smp_processor_id(); |
| PTBI _pTBI = per_cpu(pTBI, cpu); |
| struct traps_context *context; |
| |
| context = kzalloc(sizeof(*context), GFP_ATOMIC); |
| if (!context) |
| return -ENOMEM; |
| |
| memcpy(context->fnSigs, (void *)_pTBI->fnSigs, sizeof(context->fnSigs)); |
| |
| metag_traps_context = context; |
| return 0; |
| } |
| |
| int traps_restore_context(void) |
| { |
| unsigned long cpu = smp_processor_id(); |
| PTBI _pTBI = per_cpu(pTBI, cpu); |
| struct traps_context *context = metag_traps_context; |
| |
| metag_traps_context = NULL; |
| |
| memcpy((void *)_pTBI->fnSigs, context->fnSigs, sizeof(context->fnSigs)); |
| |
| kfree(context); |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_SMP |
| static inline unsigned int _get_trigger_mask(void) |
| { |
| unsigned long cpu = smp_processor_id(); |
| return per_cpu(trigger_mask, cpu); |
| } |
| |
| unsigned int get_trigger_mask(void) |
| { |
| return _get_trigger_mask(); |
| } |
| EXPORT_SYMBOL(get_trigger_mask); |
| |
| static void set_trigger_mask(unsigned int mask) |
| { |
| unsigned long cpu = smp_processor_id(); |
| per_cpu(trigger_mask, cpu) = mask; |
| } |
| |
| void arch_local_irq_enable(void) |
| { |
| preempt_disable(); |
| arch_local_irq_restore(_get_trigger_mask()); |
| preempt_enable_no_resched(); |
| } |
| EXPORT_SYMBOL(arch_local_irq_enable); |
| #else |
| static void set_trigger_mask(unsigned int mask) |
| { |
| global_trigger_mask = mask; |
| } |
| #endif |
| |
| void per_cpu_trap_init(unsigned long cpu) |
| { |
| TBIRES int_context; |
| unsigned int thread = cpu_2_hwthread_id[cpu]; |
| |
| set_trigger_mask(TBI_INTS_INIT(thread) | /* interrupts */ |
| TBI_TRIG_BIT(TBID_SIGNUM_LWK) | /* low level kick */ |
| TBI_TRIG_BIT(TBID_SIGNUM_SW1)); |
| |
| /* non-priv - use current stack */ |
| int_context.Sig.pCtx = NULL; |
| /* Start with interrupts off */ |
| int_context.Sig.TrigMask = INTS_OFF_MASK; |
| int_context.Sig.SaveMask = 0; |
| |
| /* And call __TBIASyncTrigger() */ |
| __TBIASyncTrigger(int_context); |
| } |
| |
| void __init trap_init(void) |
| { |
| unsigned long cpu = smp_processor_id(); |
| PTBI _pTBI = per_cpu(pTBI, cpu); |
| |
| _pTBI->fnSigs[TBID_SIGNUM_XXF] = fault_handler; |
| _pTBI->fnSigs[TBID_SIGNUM_SW0] = switchx_handler; |
| _pTBI->fnSigs[TBID_SIGNUM_SW1] = switch1_handler; |
| _pTBI->fnSigs[TBID_SIGNUM_SW2] = switchx_handler; |
| _pTBI->fnSigs[TBID_SIGNUM_SW3] = switchx_handler; |
| _pTBI->fnSigs[TBID_SIGNUM_LWK] = kick_handler; |
| |
| #ifdef CONFIG_METAG_META21 |
| _pTBI->fnSigs[TBID_SIGNUM_DFR] = __TBIHandleDFR; |
| _pTBI->fnSigs[TBID_SIGNUM_FPE] = fpe_handler; |
| #endif |
| |
| per_cpu_trap_init(cpu); |
| } |
| |
| void tbi_startup_interrupt(int irq) |
| { |
| unsigned long cpu = smp_processor_id(); |
| PTBI _pTBI = per_cpu(pTBI, cpu); |
| |
| BUG_ON(irq > TBID_SIGNUM_MAX); |
| |
| /* For TR1 and TR2, the thread id is encoded in the irq number */ |
| if (irq >= TBID_SIGNUM_T10 && irq < TBID_SIGNUM_TR3) |
| cpu = hwthread_id_2_cpu[(irq - TBID_SIGNUM_T10) % 4]; |
| |
| set_trigger_mask(get_trigger_mask() | TBI_TRIG_BIT(irq)); |
| |
| _pTBI->fnSigs[irq] = trigger_handler; |
| } |
| |
| void tbi_shutdown_interrupt(int irq) |
| { |
| unsigned long cpu = smp_processor_id(); |
| PTBI _pTBI = per_cpu(pTBI, cpu); |
| |
| BUG_ON(irq > TBID_SIGNUM_MAX); |
| |
| set_trigger_mask(get_trigger_mask() & ~TBI_TRIG_BIT(irq)); |
| |
| _pTBI->fnSigs[irq] = __TBIUnExpXXX; |
| } |
| |
| int ret_from_fork(TBIRES arg) |
| { |
| struct task_struct *prev = arg.Switch.pPara; |
| struct task_struct *tsk = current; |
| struct pt_regs *regs = task_pt_regs(tsk); |
| int (*fn)(void *); |
| TBIRES Next; |
| |
| schedule_tail(prev); |
| |
| if (tsk->flags & PF_KTHREAD) { |
| fn = (void *)regs->ctx.DX[4].U1; |
| BUG_ON(!fn); |
| |
| fn((void *)regs->ctx.DX[3].U1); |
| } |
| |
| if (test_syscall_work()) |
| syscall_trace_leave(regs); |
| |
| preempt_disable(); |
| |
| Next.Sig.TrigMask = get_trigger_mask(); |
| Next.Sig.SaveMask = 0; |
| Next.Sig.pCtx = ®s->ctx; |
| |
| set_gateway_tls(current->thread.tls_ptr); |
| |
| preempt_enable_no_resched(); |
| |
| /* And interrupts should come back on when we resume the real usermode |
| * code. Call __TBIASyncResume() |
| */ |
| __TBIASyncResume(tail_end(Next)); |
| /* ASyncResume should NEVER return */ |
| BUG(); |
| return 0; |
| } |
| |
| void show_trace(struct task_struct *tsk, unsigned long *sp, |
| struct pt_regs *regs) |
| { |
| unsigned long addr; |
| #ifdef CONFIG_FRAME_POINTER |
| unsigned long fp, fpnew; |
| unsigned long stack; |
| #endif |
| |
| if (regs && user_mode(regs)) |
| return; |
| |
| printk("\nCall trace: "); |
| #ifdef CONFIG_KALLSYMS |
| printk("\n"); |
| #endif |
| |
| if (!tsk) |
| tsk = current; |
| |
| #ifdef CONFIG_FRAME_POINTER |
| if (regs) { |
| print_ip_sym(regs->ctx.CurrPC); |
| fp = regs->ctx.AX[1].U0; |
| } else { |
| fp = __core_reg_get(A0FrP); |
| } |
| |
| /* detect when the frame pointer has been used for other purposes and |
| * doesn't point to the stack (it may point completely elsewhere which |
| * kstack_end may not detect). |
| */ |
| stack = (unsigned long)task_stack_page(tsk); |
| while (fp >= stack && fp + 8 <= stack + THREAD_SIZE) { |
| addr = __raw_readl((unsigned long *)(fp + 4)) - 4; |
| if (kernel_text_address(addr)) |
| print_ip_sym(addr); |
| else |
| break; |
| /* stack grows up, so frame pointers must decrease */ |
| fpnew = __raw_readl((unsigned long *)(fp + 0)); |
| if (fpnew >= fp) |
| break; |
| fp = fpnew; |
| } |
| #else |
| while (!kstack_end(sp)) { |
| addr = (*sp--) - 4; |
| if (kernel_text_address(addr)) |
| print_ip_sym(addr); |
| } |
| #endif |
| |
| printk("\n"); |
| |
| debug_show_held_locks(tsk); |
| } |
| |
| void show_stack(struct task_struct *tsk, unsigned long *sp) |
| { |
| if (!tsk) |
| tsk = current; |
| if (tsk == current) |
| sp = (unsigned long *)current_stack_pointer; |
| else |
| sp = (unsigned long *)tsk->thread.kernel_context->AX[0].U0; |
| |
| show_trace(tsk, sp, NULL); |
| } |