diff options
Diffstat (limited to 'arch/powerpc/kernel/traps.c')
-rw-r--r-- | arch/powerpc/kernel/traps.c | 130 |
1 files changed, 124 insertions, 6 deletions
diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c index 678fbff0d20..6f0ae1a9bfa 100644 --- a/arch/powerpc/kernel/traps.c +++ b/arch/powerpc/kernel/traps.c @@ -33,7 +33,9 @@ #include <linux/backlight.h> #include <linux/bug.h> #include <linux/kdebug.h> +#include <linux/debugfs.h> +#include <asm/emulated_ops.h> #include <asm/pgtable.h> #include <asm/uaccess.h> #include <asm/system.h> @@ -757,36 +759,44 @@ static int emulate_instruction(struct pt_regs *regs) /* Emulate the mfspr rD, PVR. */ if ((instword & PPC_INST_MFSPR_PVR_MASK) == PPC_INST_MFSPR_PVR) { + PPC_WARN_EMULATED(mfpvr); rd = (instword >> 21) & 0x1f; regs->gpr[rd] = mfspr(SPRN_PVR); return 0; } /* Emulating the dcba insn is just a no-op. */ - if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA) + if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA) { + PPC_WARN_EMULATED(dcba); return 0; + } /* Emulate the mcrxr insn. */ if ((instword & PPC_INST_MCRXR_MASK) == PPC_INST_MCRXR) { int shift = (instword >> 21) & 0x1c; unsigned long msk = 0xf0000000UL >> shift; + PPC_WARN_EMULATED(mcrxr); regs->ccr = (regs->ccr & ~msk) | ((regs->xer >> shift) & msk); regs->xer &= ~0xf0000000UL; return 0; } /* Emulate load/store string insn. */ - if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING) + if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING) { + PPC_WARN_EMULATED(string); return emulate_string_inst(regs, instword); + } /* Emulate the popcntb (Population Count Bytes) instruction. */ if ((instword & PPC_INST_POPCNTB_MASK) == PPC_INST_POPCNTB) { + PPC_WARN_EMULATED(popcntb); return emulate_popcntb_inst(regs, instword); } /* Emulate isel (Integer Select) instruction */ if ((instword & PPC_INST_ISEL_MASK) == PPC_INST_ISEL) { + PPC_WARN_EMULATED(isel); return emulate_isel(regs, instword); } @@ -984,6 +994,8 @@ void SoftwareEmulation(struct pt_regs *regs) #ifdef CONFIG_MATH_EMULATION errcode = do_mathemu(regs); + if (errcode >= 0) + PPC_WARN_EMULATED(math); switch (errcode) { case 0: @@ -1005,6 +1017,9 @@ void SoftwareEmulation(struct pt_regs *regs) #elif defined(CONFIG_8XX_MINIMAL_FPEMU) errcode = Soft_emulate_8xx(regs); + if (errcode >= 0) + PPC_WARN_EMULATED(8xx); + switch (errcode) { case 0: emulate_single_step(regs); @@ -1026,7 +1041,34 @@ void SoftwareEmulation(struct pt_regs *regs) void __kprobes DebugException(struct pt_regs *regs, unsigned long debug_status) { - if (debug_status & DBSR_IC) { /* instruction completion */ + /* Hack alert: On BookE, Branch Taken stops on the branch itself, while + * on server, it stops on the target of the branch. In order to simulate + * the server behaviour, we thus restart right away with a single step + * instead of stopping here when hitting a BT + */ + if (debug_status & DBSR_BT) { + regs->msr &= ~MSR_DE; + + /* Disable BT */ + mtspr(SPRN_DBCR0, mfspr(SPRN_DBCR0) & ~DBCR0_BT); + /* Clear the BT event */ + mtspr(SPRN_DBSR, DBSR_BT); + + /* Do the single step trick only when coming from userspace */ + if (user_mode(regs)) { + current->thread.dbcr0 &= ~DBCR0_BT; + current->thread.dbcr0 |= DBCR0_IDM | DBCR0_IC; + regs->msr |= MSR_DE; + return; + } + + if (notify_die(DIE_SSTEP, "block_step", regs, 5, + 5, SIGTRAP) == NOTIFY_STOP) { + return; + } + if (debugger_sstep(regs)) + return; + } else if (debug_status & DBSR_IC) { /* Instruction complete */ regs->msr &= ~MSR_DE; /* Disable instruction completion */ @@ -1042,9 +1084,8 @@ void __kprobes DebugException(struct pt_regs *regs, unsigned long debug_status) if (debugger_sstep(regs)) return; - if (user_mode(regs)) { - current->thread.dbcr0 &= ~DBCR0_IC; - } + if (user_mode(regs)) + current->thread.dbcr0 &= ~(DBCR0_IC); _exception(SIGTRAP, regs, TRAP_TRACE, regs->nip); } else if (debug_status & (DBSR_DAC1R | DBSR_DAC1W)) { @@ -1088,6 +1129,7 @@ void altivec_assist_exception(struct pt_regs *regs) flush_altivec_to_thread(current); + PPC_WARN_EMULATED(altivec); err = emulate_altivec(regs); if (err == 0) { regs->nip += 4; /* skip emulated instruction */ @@ -1286,3 +1328,79 @@ void kernel_bad_stack(struct pt_regs *regs) void __init trap_init(void) { } + + +#ifdef CONFIG_PPC_EMULATED_STATS + +#define WARN_EMULATED_SETUP(type) .type = { .name = #type } + +struct ppc_emulated ppc_emulated = { +#ifdef CONFIG_ALTIVEC + WARN_EMULATED_SETUP(altivec), +#endif + WARN_EMULATED_SETUP(dcba), + WARN_EMULATED_SETUP(dcbz), + WARN_EMULATED_SETUP(fp_pair), + WARN_EMULATED_SETUP(isel), + WARN_EMULATED_SETUP(mcrxr), + WARN_EMULATED_SETUP(mfpvr), + WARN_EMULATED_SETUP(multiple), + WARN_EMULATED_SETUP(popcntb), + WARN_EMULATED_SETUP(spe), + WARN_EMULATED_SETUP(string), + WARN_EMULATED_SETUP(unaligned), +#ifdef CONFIG_MATH_EMULATION + WARN_EMULATED_SETUP(math), +#elif defined(CONFIG_8XX_MINIMAL_FPEMU) + WARN_EMULATED_SETUP(8xx), +#endif +#ifdef CONFIG_VSX + WARN_EMULATED_SETUP(vsx), +#endif +}; + +u32 ppc_warn_emulated; + +void ppc_warn_emulated_print(const char *type) +{ + if (printk_ratelimit()) + pr_warning("%s used emulated %s instruction\n", current->comm, + type); +} + +static int __init ppc_warn_emulated_init(void) +{ + struct dentry *dir, *d; + unsigned int i; + struct ppc_emulated_entry *entries = (void *)&ppc_emulated; + + if (!powerpc_debugfs_root) + return -ENODEV; + + dir = debugfs_create_dir("emulated_instructions", + powerpc_debugfs_root); + if (!dir) + return -ENOMEM; + + d = debugfs_create_u32("do_warn", S_IRUGO | S_IWUSR, dir, + &ppc_warn_emulated); + if (!d) + goto fail; + + for (i = 0; i < sizeof(ppc_emulated)/sizeof(*entries); i++) { + d = debugfs_create_u32(entries[i].name, S_IRUGO | S_IWUSR, dir, + (u32 *)&entries[i].val.counter); + if (!d) + goto fail; + } + + return 0; + +fail: + debugfs_remove_recursive(dir); + return -ENOMEM; +} + +device_initcall(ppc_warn_emulated_init); + +#endif /* CONFIG_PPC_EMULATED_STATS */ |