Commit 79e2e096 authored by Paul Mackerras's avatar Paul Mackerras Committed by Linus Torvalds

[PATCH] ppc32: emulate obsolete instructions

This patch adds emulation in the illegal instruction handler for a couple
of old instructions that are no longer implemented in the PPC970 and later
chips.  This patch adds the code for both ppc32 and ppc64, and cleans up
the ppc64 traps.c a bit, along the lines of the ppc32 code.  It also makes
sure that the ppc64 code generates a SIGTRAP after emulating an instruction
if single-stepping is enabled.
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 1862e9f2
...@@ -357,7 +357,7 @@ void RunModeException(struct pt_regs *regs) ...@@ -357,7 +357,7 @@ void RunModeException(struct pt_regs *regs)
/* Illegal instruction emulation support. Originally written to /* Illegal instruction emulation support. Originally written to
* provide the PVR to user applications using the mfspr rd, PVR. * provide the PVR to user applications using the mfspr rd, PVR.
* Return non-zero if we can't emulate, or EFAULT if the associated * Return non-zero if we can't emulate, or -EFAULT if the associated
* memory access caused an access fault. Return zero on success. * memory access caused an access fault. Return zero on success.
* *
* There are a couple of ways to do this, either "decode" the instruction * There are a couple of ways to do this, either "decode" the instruction
...@@ -368,16 +368,19 @@ void RunModeException(struct pt_regs *regs) ...@@ -368,16 +368,19 @@ void RunModeException(struct pt_regs *regs)
#define INST_MFSPR_PVR 0x7c1f42a6 #define INST_MFSPR_PVR 0x7c1f42a6
#define INST_MFSPR_PVR_MASK 0xfc1fffff #define INST_MFSPR_PVR_MASK 0xfc1fffff
#define INST_DCBA 0x7c0005ec
#define INST_DCBA_MASK 0x7c0007fe
#define INST_MCRXR 0x7c000400
#define INST_MCRXR_MASK 0x7c0007fe
static int emulate_instruction(struct pt_regs *regs) static int emulate_instruction(struct pt_regs *regs)
{ {
u32 instword; u32 instword;
u32 rd; u32 rd;
int retval;
retval = -EINVAL;
if (!user_mode(regs)) if (!user_mode(regs))
return retval; return -EINVAL;
CHECK_FULL_REGS(regs); CHECK_FULL_REGS(regs);
if (get_user(instword, (u32 __user *)(regs->nip))) if (get_user(instword, (u32 __user *)(regs->nip)))
...@@ -388,10 +391,24 @@ static int emulate_instruction(struct pt_regs *regs) ...@@ -388,10 +391,24 @@ static int emulate_instruction(struct pt_regs *regs)
if ((instword & INST_MFSPR_PVR_MASK) == INST_MFSPR_PVR) { if ((instword & INST_MFSPR_PVR_MASK) == INST_MFSPR_PVR) {
rd = (instword >> 21) & 0x1f; rd = (instword >> 21) & 0x1f;
regs->gpr[rd] = mfspr(PVR); regs->gpr[rd] = mfspr(PVR);
retval = 0; return 0;
regs->nip += 4;
} }
return retval;
/* Emulating the dcba insn is just a no-op. */
if ((instword & INST_DCBA_MASK) == INST_DCBA)
return 0;
/* Emulate the mcrxr insn. */
if ((instword & INST_MCRXR_MASK) == INST_MCRXR) {
int shift = (instword >> 21) & 0x1c;
unsigned long msk = 0xf0000000UL >> shift;
regs->ccr = (regs->ccr & ~msk) | ((regs->xer >> shift) & msk);
regs->xer &= ~0xf0000000UL;
return 0;
}
return -EINVAL;
} }
/* /*
...@@ -528,17 +545,23 @@ void ProgramCheckException(struct pt_regs *regs) ...@@ -528,17 +545,23 @@ void ProgramCheckException(struct pt_regs *regs)
return; return;
} }
if (reason & REASON_PRIVILEGED) { /* Try to emulate it if we should. */
/* Try to emulate it if we should. */ if (reason & (REASON_ILLEGAL | REASON_PRIVILEGED)) {
if (emulate_instruction(regs) == 0) { switch (emulate_instruction(regs)) {
case 0:
regs->nip += 4;
emulate_single_step(regs); emulate_single_step(regs);
return; return;
case -EFAULT:
_exception(SIGSEGV, regs, SEGV_MAPERR, regs->nip);
return;
} }
_exception(SIGILL, regs, ILL_PRVOPC, regs->nip);
return;
} }
_exception(SIGILL, regs, ILL_ILLOPC, regs->nip); if (reason & REASON_PRIVILEGED)
_exception(SIGILL, regs, ILL_PRVOPC, regs->nip);
else
_exception(SIGILL, regs, ILL_ILLOPC, regs->nip);
} }
void SingleStepException(struct pt_regs *regs) void SingleStepException(struct pt_regs *regs)
......
...@@ -134,14 +134,20 @@ int die(const char *str, struct pt_regs *regs, long err) ...@@ -134,14 +134,20 @@ int die(const char *str, struct pt_regs *regs, long err)
} }
static void static void
_exception(int signr, siginfo_t *info, struct pt_regs *regs) _exception(int signr, struct pt_regs *regs, int code, unsigned long addr)
{ {
siginfo_t info;
if (!user_mode(regs)) { if (!user_mode(regs)) {
if (die("Exception in kernel mode", regs, signr)) if (die("Exception in kernel mode", regs, signr))
return; return;
} }
force_sig_info(signr, info, current); memset(&info, 0, sizeof(info));
info.si_signo = signr;
info.si_code = code;
info.si_addr = (void __user *) addr;
force_sig_info(signr, &info, current);
} }
#ifdef CONFIG_PPC_PSERIES #ifdef CONFIG_PPC_PSERIES
...@@ -213,8 +219,6 @@ SystemResetException(struct pt_regs *regs) ...@@ -213,8 +219,6 @@ SystemResetException(struct pt_regs *regs)
*/ */
static int recover_mce(struct pt_regs *regs, struct rtas_error_log err) static int recover_mce(struct pt_regs *regs, struct rtas_error_log err)
{ {
siginfo_t info;
if (err.disposition == DISP_FULLY_RECOVERED) { if (err.disposition == DISP_FULLY_RECOVERED) {
/* Platform corrected itself */ /* Platform corrected itself */
return 1; return 1;
...@@ -226,14 +230,10 @@ static int recover_mce(struct pt_regs *regs, struct rtas_error_log err) ...@@ -226,14 +230,10 @@ static int recover_mce(struct pt_regs *regs, struct rtas_error_log err)
err.type == TYPE_ECC_UNCORR && err.type == TYPE_ECC_UNCORR &&
!(current->pid == 0 || current->pid == 1)) { !(current->pid == 0 || current->pid == 1)) {
/* Kill off a user process with an ECC error */ /* Kill off a user process with an ECC error */
info.si_signo = SIGBUS;
info.si_errno = 0;
/* XXX something better for ECC error? */
info.si_code = BUS_ADRERR;
info.si_addr = (void __user *)regs->nip;
printk(KERN_ERR "MCE: uncorrectable ecc error for pid %d\n", printk(KERN_ERR "MCE: uncorrectable ecc error for pid %d\n",
current->pid); current->pid);
_exception(SIGBUS, &info, regs); /* XXX something better for ECC error? */
_exception(SIGBUS, regs, BUS_ADRERR, regs->nip);
return 1; return 1;
} }
return 0; return 0;
...@@ -278,35 +278,46 @@ MachineCheckException(struct pt_regs *regs) ...@@ -278,35 +278,46 @@ MachineCheckException(struct pt_regs *regs)
void void
UnknownException(struct pt_regs *regs) UnknownException(struct pt_regs *regs)
{ {
siginfo_t info;
printk("Bad trap at PC: %lx, SR: %lx, vector=%lx\n", printk("Bad trap at PC: %lx, SR: %lx, vector=%lx\n",
regs->nip, regs->msr, regs->trap); regs->nip, regs->msr, regs->trap);
info.si_signo = SIGTRAP; _exception(SIGTRAP, regs, 0, 0);
info.si_errno = 0;
info.si_code = 0;
info.si_addr = NULL;
_exception(SIGTRAP, &info, regs);
} }
void void
InstructionBreakpointException(struct pt_regs *regs) InstructionBreakpointException(struct pt_regs *regs)
{ {
siginfo_t info;
if (debugger_iabr_match(regs)) if (debugger_iabr_match(regs))
return; return;
info.si_signo = SIGTRAP; _exception(SIGTRAP, regs, TRAP_BRKPT, regs->nip);
info.si_errno = 0; }
info.si_code = TRAP_BRKPT;
info.si_addr = (void __user *)regs->nip; void
_exception(SIGTRAP, &info, regs); SingleStepException(struct pt_regs *regs)
{
regs->msr &= ~MSR_SE; /* Turn off 'trace' bit */
if (debugger_sstep(regs))
return;
_exception(SIGTRAP, regs, TRAP_TRACE, regs->nip);
}
/*
* After we have successfully emulated an instruction, we have to
* check if the instruction was being single-stepped, and if so,
* pretend we got a single-step exception. This was pointed out
* by Kumar Gala. -- paulus
*/
static inline void emulate_single_step(struct pt_regs *regs)
{
if (regs->msr & MSR_SE)
SingleStepException(regs);
} }
static void parse_fpe(struct pt_regs *regs) static void parse_fpe(struct pt_regs *regs)
{ {
siginfo_t info; int code = 0;
unsigned long fpscr; unsigned long fpscr;
flush_fp_to_thread(current); flush_fp_to_thread(current);
...@@ -315,31 +326,84 @@ static void parse_fpe(struct pt_regs *regs) ...@@ -315,31 +326,84 @@ static void parse_fpe(struct pt_regs *regs)
/* Invalid operation */ /* Invalid operation */
if ((fpscr & FPSCR_VE) && (fpscr & FPSCR_VX)) if ((fpscr & FPSCR_VE) && (fpscr & FPSCR_VX))
info.si_code = FPE_FLTINV; code = FPE_FLTINV;
/* Overflow */ /* Overflow */
else if ((fpscr & FPSCR_OE) && (fpscr & FPSCR_OX)) else if ((fpscr & FPSCR_OE) && (fpscr & FPSCR_OX))
info.si_code = FPE_FLTOVF; code = FPE_FLTOVF;
/* Underflow */ /* Underflow */
else if ((fpscr & FPSCR_UE) && (fpscr & FPSCR_UX)) else if ((fpscr & FPSCR_UE) && (fpscr & FPSCR_UX))
info.si_code = FPE_FLTUND; code = FPE_FLTUND;
/* Divide by zero */ /* Divide by zero */
else if ((fpscr & FPSCR_ZE) && (fpscr & FPSCR_ZX)) else if ((fpscr & FPSCR_ZE) && (fpscr & FPSCR_ZX))
info.si_code = FPE_FLTDIV; code = FPE_FLTDIV;
/* Inexact result */ /* Inexact result */
else if ((fpscr & FPSCR_XE) && (fpscr & FPSCR_XX)) else if ((fpscr & FPSCR_XE) && (fpscr & FPSCR_XX))
info.si_code = FPE_FLTRES; code = FPE_FLTRES;
_exception(SIGFPE, regs, code, regs->nip);
}
/*
* Illegal instruction emulation support. Return non-zero if we can't
* emulate, or -EFAULT if the associated memory access caused an access
* fault. Return zero on success.
*/
#define INST_DCBA 0x7c0005ec
#define INST_DCBA_MASK 0x7c0007fe
#define INST_MCRXR 0x7c000400
#define INST_MCRXR_MASK 0x7c0007fe
static int emulate_instruction(struct pt_regs *regs)
{
unsigned int instword;
if (!user_mode(regs))
return -EINVAL;
CHECK_FULL_REGS(regs);
if (get_user(instword, (unsigned int __user *)(regs->nip)))
return -EFAULT;
/* Emulating the dcba insn is just a no-op. */
if ((instword & INST_DCBA_MASK) == INST_DCBA) {
static int warned;
if (!warned) {
printk(KERN_WARNING
"process %d (%s) uses obsolete 'dcba' insn\n",
current->pid, current->comm);
warned = 1;
}
return 0;
}
/* Emulate the mcrxr insn. */
if ((instword & INST_MCRXR_MASK) == INST_MCRXR) {
static int warned;
unsigned int shift;
if (!warned) {
printk(KERN_WARNING
"process %d (%s) uses obsolete 'mcrxr' insn\n",
current->pid, current->comm);
warned = 1;
}
else shift = (instword >> 21) & 0x1c;
info.si_code = 0; regs->ccr &= ~(0xf0000000 >> shift);
regs->ccr |= (regs->xer & 0xf0000000) >> shift;
regs->xer &= ~0xf0000000;
return 0;
}
info.si_signo = SIGFPE; return -EINVAL;
info.si_errno = 0;
info.si_addr = (void __user *)regs->nip;
_exception(SIGFPE, &info, regs);
} }
/* /*
...@@ -395,20 +459,14 @@ check_bug_trap(struct pt_regs *regs) ...@@ -395,20 +459,14 @@ check_bug_trap(struct pt_regs *regs)
void void
ProgramCheckException(struct pt_regs *regs) ProgramCheckException(struct pt_regs *regs)
{ {
siginfo_t info;
if (regs->msr & 0x100000) { if (regs->msr & 0x100000) {
/* IEEE FP exception */ /* IEEE FP exception */
parse_fpe(regs); parse_fpe(regs);
} else if (regs->msr & 0x40000) { } else if (regs->msr & 0x40000) {
/* Privileged instruction */ /* Privileged instruction */
_exception(SIGILL, regs, ILL_PRVOPC, regs->nip);
info.si_signo = SIGILL;
info.si_errno = 0;
info.si_code = ILL_PRVOPC;
info.si_addr = (void __user *)regs->nip;
_exception(SIGILL, &info, regs);
} else if (regs->msr & 0x20000) { } else if (regs->msr & 0x20000) {
/* trap exception */ /* trap exception */
...@@ -419,19 +477,24 @@ ProgramCheckException(struct pt_regs *regs) ...@@ -419,19 +477,24 @@ ProgramCheckException(struct pt_regs *regs)
regs->nip += 4; regs->nip += 4;
return; return;
} }
info.si_signo = SIGTRAP; _exception(SIGTRAP, regs, TRAP_BRKPT, regs->nip);
info.si_errno = 0;
info.si_code = TRAP_BRKPT;
info.si_addr = (void __user *)regs->nip;
_exception(SIGTRAP, &info, regs);
} else { } else {
/* Illegal instruction */ /* Illegal instruction; try to emulate it. */
switch (emulate_instruction(regs)) {
case 0:
regs->nip += 4;
emulate_single_step(regs);
break;
info.si_signo = SIGILL; case -EFAULT:
info.si_errno = 0; _exception(SIGSEGV, regs, SEGV_MAPERR, regs->nip);
info.si_code = ILL_ILLTRP; break;
info.si_addr = (void __user *)regs->nip;
_exception(SIGILL, &info, regs); default:
_exception(SIGILL, regs, ILL_ILLOPC, regs->nip);
break;
}
} }
} }
...@@ -448,13 +511,7 @@ void AltivecUnavailableException(struct pt_regs *regs) ...@@ -448,13 +511,7 @@ void AltivecUnavailableException(struct pt_regs *regs)
if (user_mode(regs)) { if (user_mode(regs)) {
/* A user program has executed an altivec instruction, /* A user program has executed an altivec instruction,
but this kernel doesn't support altivec. */ but this kernel doesn't support altivec. */
siginfo_t info; _exception(SIGILL, regs, ILL_ILLOPC, regs->nip);
memset(&info, 0, sizeof(info));
info.si_signo = SIGILL;
info.si_code = ILL_ILLOPC;
info.si_addr = (void *) regs->nip;
_exception(SIGILL, &info, regs);
return; return;
} }
#endif #endif
...@@ -463,35 +520,6 @@ void AltivecUnavailableException(struct pt_regs *regs) ...@@ -463,35 +520,6 @@ void AltivecUnavailableException(struct pt_regs *regs)
die("Unrecoverable VMX/Altivec Unavailable Exception", regs, SIGABRT); die("Unrecoverable VMX/Altivec Unavailable Exception", regs, SIGABRT);
} }
void
SingleStepException(struct pt_regs *regs)
{
siginfo_t info;
regs->msr &= ~MSR_SE; /* Turn off 'trace' bit */
if (debugger_sstep(regs))
return;
info.si_signo = SIGTRAP;
info.si_errno = 0;
info.si_code = TRAP_TRACE;
info.si_addr = (void __user *)regs->nip;
_exception(SIGTRAP, &info, regs);
}
/*
* After we have successfully emulated an instruction, we have to
* check if the instruction was being single-stepped, and if so,
* pretend we got a single-step exception. This was pointed out
* by Kumar Gala. -- paulus
*/
static inline void emulate_single_step(struct pt_regs *regs)
{
if (regs->msr & MSR_SE)
SingleStepException(regs);
}
static void dummy_perf(struct pt_regs *regs) static void dummy_perf(struct pt_regs *regs)
{ {
} }
...@@ -508,7 +536,6 @@ void ...@@ -508,7 +536,6 @@ void
AlignmentException(struct pt_regs *regs) AlignmentException(struct pt_regs *regs)
{ {
int fixed; int fixed;
siginfo_t info;
fixed = fix_alignment(regs); fixed = fix_alignment(regs);
...@@ -521,11 +548,7 @@ AlignmentException(struct pt_regs *regs) ...@@ -521,11 +548,7 @@ AlignmentException(struct pt_regs *regs)
/* Operand address was bad */ /* Operand address was bad */
if (fixed == -EFAULT) { if (fixed == -EFAULT) {
if (user_mode(regs)) { if (user_mode(regs)) {
info.si_signo = SIGSEGV; _exception(SIGSEGV, regs, SEGV_MAPERR, regs->dar);
info.si_errno = 0;
info.si_code = SEGV_MAPERR;
info.si_addr = (void __user *)regs->dar;
force_sig_info(SIGSEGV, &info, current);
} else { } else {
/* Search exception table */ /* Search exception table */
bad_page_fault(regs, regs->dar, SIGSEGV); bad_page_fault(regs, regs->dar, SIGSEGV);
...@@ -534,11 +557,7 @@ AlignmentException(struct pt_regs *regs) ...@@ -534,11 +557,7 @@ AlignmentException(struct pt_regs *regs)
return; return;
} }
info.si_signo = SIGBUS; _exception(SIGBUS, regs, BUS_ADRALN, regs->nip);
info.si_errno = 0;
info.si_code = BUS_ADRALN;
info.si_addr = (void __user *)regs->nip;
_exception(SIGBUS, &info, regs);
} }
#ifdef CONFIG_ALTIVEC #ifdef CONFIG_ALTIVEC
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment