summaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel/branch.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-02-22 04:41:38 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2015-02-22 04:41:38 +0100
commita135c717d5cdb311cff7661af4c17fef0562e590 (patch)
tree830a276ee80b95f02ae243c641690c9f2014922d /arch/mips/kernel/branch.c
parentMerge tag 'fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/... (diff)
parentMIPS: sead3: Corrected get_c0_perfcount_int (diff)
downloadlinux-a135c717d5cdb311cff7661af4c17fef0562e590.tar.xz
linux-a135c717d5cdb311cff7661af4c17fef0562e590.zip
Merge branch 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus
Pull MIPS updates from Ralf Baechle: "This is the main pull request for MIPS: - a number of fixes that didn't make the 3.19 release. - a number of cleanups. - preliminary support for Cavium's Octeon 3 SOCs which feature up to 48 MIPS64 R3 cores with FPU and hardware virtualization. - support for MIPS R6 processors. Revision 6 of the MIPS architecture is a major revision of the MIPS architecture which does away with many of original sins of the architecture such as branch delay slots. This and other changes in R6 require major changes throughout the entire MIPS core architecture code and make up for the lion share of this pull request. - finally some preparatory work for eXtendend Physical Address support, which allows support of up to 40 bit of physical address space on 32 bit processors" [ Ahh, MIPS can't leave the PAE brain damage alone. It's like every CPU architect has to make that mistake, but pee in the snow by changing the TLA. But whether it's called PAE, LPAE or XPA, it's horrid crud - Linus ] * 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus: (114 commits) MIPS: sead3: Corrected get_c0_perfcount_int MIPS: mm: Remove dead macro definitions MIPS: OCTEON: irq: add CIB and other fixes MIPS: OCTEON: Don't do acknowledge operations for level triggered irqs. MIPS: OCTEON: More OCTEONIII support MIPS: OCTEON: Remove setting of processor specific CVMCTL icache bits. MIPS: OCTEON: Core-15169 Workaround and general CVMSEG cleanup. MIPS: OCTEON: Update octeon-model.h code for new SoCs. MIPS: OCTEON: Implement DCache errata workaround for all CN6XXX MIPS: OCTEON: Add little-endian support to asm/octeon/octeon.h MIPS: OCTEON: Implement the core-16057 workaround MIPS: OCTEON: Delete unused COP2 saving code MIPS: OCTEON: Use correct instruction to read 64-bit COP0 register MIPS: OCTEON: Save and restore CP2 SHA3 state MIPS: OCTEON: Fix FP context save. MIPS: OCTEON: Save/Restore wider multiply registers in OCTEON III CPUs MIPS: boot: Provide more uImage options MIPS: Remove unneeded #ifdef __KERNEL__ from asm/processor.h MIPS: ip22-gio: Remove legacy suspend/resume support mips: pci: Add ifdef around pci_proc_domain ...
Diffstat (limited to 'arch/mips/kernel/branch.c')
-rw-r--r--arch/mips/kernel/branch.c288
1 files changed, 250 insertions, 38 deletions
diff --git a/arch/mips/kernel/branch.c b/arch/mips/kernel/branch.c
index 4d7d99d601cc..c2e0f45ddf6c 100644
--- a/arch/mips/kernel/branch.c
+++ b/arch/mips/kernel/branch.c
@@ -16,6 +16,7 @@
#include <asm/fpu.h>
#include <asm/fpu_emulator.h>
#include <asm/inst.h>
+#include <asm/mips-r2-to-r6-emul.h>
#include <asm/ptrace.h>
#include <asm/uaccess.h>
@@ -399,11 +400,21 @@ int __MIPS16e_compute_return_epc(struct pt_regs *regs)
* @returns: -EFAULT on error and forces SIGBUS, and on success
* returns 0 or BRANCH_LIKELY_TAKEN as appropriate after
* evaluating the branch.
+ *
+ * MIPS R6 Compact branches and forbidden slots:
+ * Compact branches do not throw exceptions because they do
+ * not have delay slots. The forbidden slot instruction ($PC+4)
+ * is only executed if the branch was not taken. Otherwise the
+ * forbidden slot is skipped entirely. This means that the
+ * only possible reason to be here because of a MIPS R6 compact
+ * branch instruction is that the forbidden slot has thrown one.
+ * In that case the branch was not taken, so the EPC can be safely
+ * set to EPC + 8.
*/
int __compute_return_epc_for_insn(struct pt_regs *regs,
union mips_instruction insn)
{
- unsigned int bit, fcr31, dspcontrol;
+ unsigned int bit, fcr31, dspcontrol, reg;
long epc = regs->cp0_epc;
int ret = 0;
@@ -417,6 +428,8 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
regs->regs[insn.r_format.rd] = epc + 8;
/* Fall through */
case jr_op:
+ if (NO_R6EMU && insn.r_format.func == jr_op)
+ goto sigill_r6;
regs->cp0_epc = regs->regs[insn.r_format.rs];
break;
}
@@ -429,8 +442,10 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
*/
case bcond_op:
switch (insn.i_format.rt) {
- case bltz_op:
case bltzl_op:
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case bltz_op:
if ((long)regs->regs[insn.i_format.rs] < 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
if (insn.i_format.rt == bltzl_op)
@@ -440,8 +455,10 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
regs->cp0_epc = epc;
break;
- case bgez_op:
case bgezl_op:
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case bgez_op:
if ((long)regs->regs[insn.i_format.rs] >= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
if (insn.i_format.rt == bgezl_op)
@@ -453,7 +470,29 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
case bltzal_op:
case bltzall_op:
+ if (NO_R6EMU && (insn.i_format.rs ||
+ insn.i_format.rt == bltzall_op)) {
+ ret = -SIGILL;
+ break;
+ }
regs->regs[31] = epc + 8;
+ /*
+ * OK we are here either because we hit a NAL
+ * instruction or because we are emulating an
+ * old bltzal{,l} one. Lets figure out what the
+ * case really is.
+ */
+ if (!insn.i_format.rs) {
+ /*
+ * NAL or BLTZAL with rs == 0
+ * Doesn't matter if we are R6 or not. The
+ * result is the same
+ */
+ regs->cp0_epc += 4 +
+ (insn.i_format.simmediate << 2);
+ break;
+ }
+ /* Now do the real thing for non-R6 BLTZAL{,L} */
if ((long)regs->regs[insn.i_format.rs] < 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
if (insn.i_format.rt == bltzall_op)
@@ -465,7 +504,29 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
case bgezal_op:
case bgezall_op:
+ if (NO_R6EMU && (insn.i_format.rs ||
+ insn.i_format.rt == bgezall_op)) {
+ ret = -SIGILL;
+ break;
+ }
regs->regs[31] = epc + 8;
+ /*
+ * OK we are here either because we hit a BAL
+ * instruction or because we are emulating an
+ * old bgezal{,l} one. Lets figure out what the
+ * case really is.
+ */
+ if (!insn.i_format.rs) {
+ /*
+ * BAL or BGEZAL with rs == 0
+ * Doesn't matter if we are R6 or not. The
+ * result is the same
+ */
+ regs->cp0_epc += 4 +
+ (insn.i_format.simmediate << 2);
+ break;
+ }
+ /* Now do the real thing for non-R6 BGEZAL{,L} */
if ((long)regs->regs[insn.i_format.rs] >= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
if (insn.i_format.rt == bgezall_op)
@@ -477,7 +538,7 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
case bposge32_op:
if (!cpu_has_dsp)
- goto sigill;
+ goto sigill_dsp;
dspcontrol = rddsp(0x01);
@@ -508,8 +569,10 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
/*
* These are conditional and in i_format.
*/
- case beq_op:
case beql_op:
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case beq_op:
if (regs->regs[insn.i_format.rs] ==
regs->regs[insn.i_format.rt]) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
@@ -520,8 +583,10 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
regs->cp0_epc = epc;
break;
- case bne_op:
case bnel_op:
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case bne_op:
if (regs->regs[insn.i_format.rs] !=
regs->regs[insn.i_format.rt]) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
@@ -532,8 +597,31 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
regs->cp0_epc = epc;
break;
- case blez_op: /* not really i_format */
- case blezl_op:
+ case blezl_op: /* not really i_format */
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case blez_op:
+ /*
+ * Compact branches for R6 for the
+ * blez and blezl opcodes.
+ * BLEZ | rs = 0 | rt != 0 == BLEZALC
+ * BLEZ | rs = rt != 0 == BGEZALC
+ * BLEZ | rs != 0 | rt != 0 == BGEUC
+ * BLEZL | rs = 0 | rt != 0 == BLEZC
+ * BLEZL | rs = rt != 0 == BGEZC
+ * BLEZL | rs != 0 | rt != 0 == BGEC
+ *
+ * For real BLEZ{,L}, rt is always 0.
+ */
+
+ if (cpu_has_mips_r6 && insn.i_format.rt) {
+ if ((insn.i_format.opcode == blez_op) &&
+ ((!insn.i_format.rs && insn.i_format.rt) ||
+ (insn.i_format.rs == insn.i_format.rt)))
+ regs->regs[31] = epc + 4;
+ regs->cp0_epc += 8;
+ break;
+ }
/* rt field assumed to be zero */
if ((long)regs->regs[insn.i_format.rs] <= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
@@ -544,8 +632,32 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
regs->cp0_epc = epc;
break;
- case bgtz_op:
case bgtzl_op:
+ if (NO_R6EMU)
+ goto sigill_r6;
+ case bgtz_op:
+ /*
+ * Compact branches for R6 for the
+ * bgtz and bgtzl opcodes.
+ * BGTZ | rs = 0 | rt != 0 == BGTZALC
+ * BGTZ | rs = rt != 0 == BLTZALC
+ * BGTZ | rs != 0 | rt != 0 == BLTUC
+ * BGTZL | rs = 0 | rt != 0 == BGTZC
+ * BGTZL | rs = rt != 0 == BLTZC
+ * BGTZL | rs != 0 | rt != 0 == BLTC
+ *
+ * *ZALC varint for BGTZ &&& rt != 0
+ * For real GTZ{,L}, rt is always 0.
+ */
+ if (cpu_has_mips_r6 && insn.i_format.rt) {
+ if ((insn.i_format.opcode == blez_op) &&
+ ((!insn.i_format.rs && insn.i_format.rt) ||
+ (insn.i_format.rs == insn.i_format.rt)))
+ regs->regs[31] = epc + 4;
+ regs->cp0_epc += 8;
+ break;
+ }
+
/* rt field assumed to be zero */
if ((long)regs->regs[insn.i_format.rs] > 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
@@ -560,40 +672,83 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
* And now the FPA/cp1 branch instructions.
*/
case cop1_op:
- preempt_disable();
- if (is_fpu_owner())
- fcr31 = read_32bit_cp1_register(CP1_STATUS);
- else
- fcr31 = current->thread.fpu.fcr31;
- preempt_enable();
-
- bit = (insn.i_format.rt >> 2);
- bit += (bit != 0);
- bit += 23;
- switch (insn.i_format.rt & 3) {
- case 0: /* bc1f */
- case 2: /* bc1fl */
- if (~fcr31 & (1 << bit)) {
- epc = epc + 4 + (insn.i_format.simmediate << 2);
- if (insn.i_format.rt == 2)
- ret = BRANCH_LIKELY_TAKEN;
- } else
+ if (cpu_has_mips_r6 &&
+ ((insn.i_format.rs == bc1eqz_op) ||
+ (insn.i_format.rs == bc1nez_op))) {
+ if (!used_math()) { /* First time FPU user */
+ ret = init_fpu();
+ if (ret && NO_R6EMU) {
+ ret = -ret;
+ break;
+ }
+ ret = 0;
+ set_used_math();
+ }
+ lose_fpu(1); /* Save FPU state for the emulator. */
+ reg = insn.i_format.rt;
+ bit = 0;
+ switch (insn.i_format.rs) {
+ case bc1eqz_op:
+ /* Test bit 0 */
+ if (get_fpr32(&current->thread.fpu.fpr[reg], 0)
+ & 0x1)
+ bit = 1;
+ break;
+ case bc1nez_op:
+ /* Test bit 0 */
+ if (!(get_fpr32(&current->thread.fpu.fpr[reg], 0)
+ & 0x1))
+ bit = 1;
+ break;
+ }
+ own_fpu(1);
+ if (bit)
+ epc = epc + 4 +
+ (insn.i_format.simmediate << 2);
+ else
epc += 8;
regs->cp0_epc = epc;
+
break;
+ } else {
- case 1: /* bc1t */
- case 3: /* bc1tl */
- if (fcr31 & (1 << bit)) {
- epc = epc + 4 + (insn.i_format.simmediate << 2);
- if (insn.i_format.rt == 3)
- ret = BRANCH_LIKELY_TAKEN;
- } else
- epc += 8;
- regs->cp0_epc = epc;
+ preempt_disable();
+ if (is_fpu_owner())
+ fcr31 = read_32bit_cp1_register(CP1_STATUS);
+ else
+ fcr31 = current->thread.fpu.fcr31;
+ preempt_enable();
+
+ bit = (insn.i_format.rt >> 2);
+ bit += (bit != 0);
+ bit += 23;
+ switch (insn.i_format.rt & 3) {
+ case 0: /* bc1f */
+ case 2: /* bc1fl */
+ if (~fcr31 & (1 << bit)) {
+ epc = epc + 4 +
+ (insn.i_format.simmediate << 2);
+ if (insn.i_format.rt == 2)
+ ret = BRANCH_LIKELY_TAKEN;
+ } else
+ epc += 8;
+ regs->cp0_epc = epc;
+ break;
+
+ case 1: /* bc1t */
+ case 3: /* bc1tl */
+ if (fcr31 & (1 << bit)) {
+ epc = epc + 4 +
+ (insn.i_format.simmediate << 2);
+ if (insn.i_format.rt == 3)
+ ret = BRANCH_LIKELY_TAKEN;
+ } else
+ epc += 8;
+ regs->cp0_epc = epc;
+ break;
+ }
break;
}
- break;
#ifdef CONFIG_CPU_CAVIUM_OCTEON
case lwc2_op: /* This is bbit0 on Octeon */
if ((regs->regs[insn.i_format.rs] & (1ull<<insn.i_format.rt))
@@ -626,15 +781,72 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
epc += 8;
regs->cp0_epc = epc;
break;
+#else
+ case bc6_op:
+ /* Only valid for MIPS R6 */
+ if (!cpu_has_mips_r6) {
+ ret = -SIGILL;
+ break;
+ }
+ regs->cp0_epc += 8;
+ break;
+ case balc6_op:
+ if (!cpu_has_mips_r6) {
+ ret = -SIGILL;
+ break;
+ }
+ /* Compact branch: BALC */
+ regs->regs[31] = epc + 4;
+ epc += 4 + (insn.i_format.simmediate << 2);
+ regs->cp0_epc = epc;
+ break;
+ case beqzcjic_op:
+ if (!cpu_has_mips_r6) {
+ ret = -SIGILL;
+ break;
+ }
+ /* Compact branch: BEQZC || JIC */
+ regs->cp0_epc += 8;
+ break;
+ case bnezcjialc_op:
+ if (!cpu_has_mips_r6) {
+ ret = -SIGILL;
+ break;
+ }
+ /* Compact branch: BNEZC || JIALC */
+ if (insn.i_format.rs)
+ regs->regs[31] = epc + 4;
+ regs->cp0_epc += 8;
+ break;
#endif
+ case cbcond0_op:
+ case cbcond1_op:
+ /* Only valid for MIPS R6 */
+ if (!cpu_has_mips_r6) {
+ ret = -SIGILL;
+ break;
+ }
+ /*
+ * Compact branches:
+ * bovc, beqc, beqzalc, bnvc, bnec, bnezlac
+ */
+ if (insn.i_format.rt && !insn.i_format.rs)
+ regs->regs[31] = epc + 4;
+ regs->cp0_epc += 8;
+ break;
}
return ret;
-sigill:
+sigill_dsp:
printk("%s: DSP branch but not DSP ASE - sending SIGBUS.\n", current->comm);
force_sig(SIGBUS, current);
return -EFAULT;
+sigill_r6:
+ pr_info("%s: R2 branch but r2-to-r6 emulator is not preset - sending SIGILL.\n",
+ current->comm);
+ force_sig(SIGILL, current);
+ return -EFAULT;
}
EXPORT_SYMBOL_GPL(__compute_return_epc_for_insn);