| /* | |
| * 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); | |
| } |