aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel/branch.c
diff options
context:
space:
mode:
authorManeesh Soni <manesoni@cisco.com>2011-11-08 06:37:11 -0500
committerRalf Baechle <ralf@linux-mips.org>2011-12-07 17:04:03 -0500
commitd8d4e3ae0b5c179c0bfd3f0af5b352d13bea9cfa (patch)
treee0341b09add0b344ce078595d3ad5145beda0cfd /arch/mips/kernel/branch.c
parent9233c1ee71bdd3c8a918c8e17026cf3f7d99c90b (diff)
MIPS Kprobes: Refactor branch emulation
This patch refactors MIPS branch emulation code so as to allow skipping delay slot instruction in case of branch likely instructions when branch is not taken. This is useful for keeping the code common for use cases like kprobes where one would like to handle the branch instructions keeping the delay slot instuction also in picture for branch likely instructions. Also allow emulation when instruction to be decoded is not at pt_regs->cp0_epc as in case of kprobes where pt_regs->cp0_epc points to the breakpoint instruction. The patch also exports the function for modules. Signed-off-by: Maneesh Soni <manesoni@cisco.com> Signed-off-by: Victor Kamensky <kamensky@cisco.com> Cc: David Daney <david.daney@cavium.com> Cc: ananth@in.ibm.com Cc: linux-kernel@vger.kernel.org Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/2913/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/kernel/branch.c')
-rw-r--r--arch/mips/kernel/branch.c128
1 files changed, 84 insertions, 44 deletions
diff --git a/arch/mips/kernel/branch.c b/arch/mips/kernel/branch.c
index 32103cc2a257..4d735d0e58f5 100644
--- a/arch/mips/kernel/branch.c
+++ b/arch/mips/kernel/branch.c
@@ -9,6 +9,7 @@
9#include <linux/kernel.h> 9#include <linux/kernel.h>
10#include <linux/sched.h> 10#include <linux/sched.h>
11#include <linux/signal.h> 11#include <linux/signal.h>
12#include <linux/module.h>
12#include <asm/branch.h> 13#include <asm/branch.h>
13#include <asm/cpu.h> 14#include <asm/cpu.h>
14#include <asm/cpu-features.h> 15#include <asm/cpu-features.h>
@@ -17,28 +18,22 @@
17#include <asm/ptrace.h> 18#include <asm/ptrace.h>
18#include <asm/uaccess.h> 19#include <asm/uaccess.h>
19 20
20/* 21/**
21 * Compute the return address and do emulate branch simulation, if required. 22 * __compute_return_epc_for_insn - Computes the return address and do emulate
23 * branch simulation, if required.
24 *
25 * @regs: Pointer to pt_regs
26 * @insn: branch instruction to decode
27 * @returns: -EFAULT on error and forces SIGBUS, and on success
28 * returns 0 or BRANCH_LIKELY_TAKEN as appropriate after
29 * evaluating the branch.
22 */ 30 */
23int __compute_return_epc(struct pt_regs *regs) 31int __compute_return_epc_for_insn(struct pt_regs *regs,
32 union mips_instruction insn)
24{ 33{
25 unsigned int __user *addr;
26 unsigned int bit, fcr31, dspcontrol; 34 unsigned int bit, fcr31, dspcontrol;
27 long epc; 35 long epc = regs->cp0_epc;
28 union mips_instruction insn; 36 int ret = 0;
29
30 epc = regs->cp0_epc;
31 if (epc & 3)
32 goto unaligned;
33
34 /*
35 * Read the instruction
36 */
37 addr = (unsigned int __user *) epc;
38 if (__get_user(insn.word, addr)) {
39 force_sig(SIGSEGV, current);
40 return -EFAULT;
41 }
42 37
43 switch (insn.i_format.opcode) { 38 switch (insn.i_format.opcode) {
44 /* 39 /*
@@ -64,18 +59,22 @@ int __compute_return_epc(struct pt_regs *regs)
64 switch (insn.i_format.rt) { 59 switch (insn.i_format.rt) {
65 case bltz_op: 60 case bltz_op:
66 case bltzl_op: 61 case bltzl_op:
67 if ((long)regs->regs[insn.i_format.rs] < 0) 62 if ((long)regs->regs[insn.i_format.rs] < 0) {
68 epc = epc + 4 + (insn.i_format.simmediate << 2); 63 epc = epc + 4 + (insn.i_format.simmediate << 2);
69 else 64 if (insn.i_format.rt == bltzl_op)
65 ret = BRANCH_LIKELY_TAKEN;
66 } else
70 epc += 8; 67 epc += 8;
71 regs->cp0_epc = epc; 68 regs->cp0_epc = epc;
72 break; 69 break;
73 70
74 case bgez_op: 71 case bgez_op:
75 case bgezl_op: 72 case bgezl_op:
76 if ((long)regs->regs[insn.i_format.rs] >= 0) 73 if ((long)regs->regs[insn.i_format.rs] >= 0) {
77 epc = epc + 4 + (insn.i_format.simmediate << 2); 74 epc = epc + 4 + (insn.i_format.simmediate << 2);
78 else 75 if (insn.i_format.rt == bgezl_op)
76 ret = BRANCH_LIKELY_TAKEN;
77 } else
79 epc += 8; 78 epc += 8;
80 regs->cp0_epc = epc; 79 regs->cp0_epc = epc;
81 break; 80 break;
@@ -83,9 +82,11 @@ int __compute_return_epc(struct pt_regs *regs)
83 case bltzal_op: 82 case bltzal_op:
84 case bltzall_op: 83 case bltzall_op:
85 regs->regs[31] = epc + 8; 84 regs->regs[31] = epc + 8;
86 if ((long)regs->regs[insn.i_format.rs] < 0) 85 if ((long)regs->regs[insn.i_format.rs] < 0) {
87 epc = epc + 4 + (insn.i_format.simmediate << 2); 86 epc = epc + 4 + (insn.i_format.simmediate << 2);
88 else 87 if (insn.i_format.rt == bltzall_op)
88 ret = BRANCH_LIKELY_TAKEN;
89 } else
89 epc += 8; 90 epc += 8;
90 regs->cp0_epc = epc; 91 regs->cp0_epc = epc;
91 break; 92 break;
@@ -93,12 +94,15 @@ int __compute_return_epc(struct pt_regs *regs)
93 case bgezal_op: 94 case bgezal_op:
94 case bgezall_op: 95 case bgezall_op:
95 regs->regs[31] = epc + 8; 96 regs->regs[31] = epc + 8;
96 if ((long)regs->regs[insn.i_format.rs] >= 0) 97 if ((long)regs->regs[insn.i_format.rs] >= 0) {
97 epc = epc + 4 + (insn.i_format.simmediate << 2); 98 epc = epc + 4 + (insn.i_format.simmediate << 2);
98 else 99 if (insn.i_format.rt == bgezall_op)
100 ret = BRANCH_LIKELY_TAKEN;
101 } else
99 epc += 8; 102 epc += 8;
100 regs->cp0_epc = epc; 103 regs->cp0_epc = epc;
101 break; 104 break;
105
102 case bposge32_op: 106 case bposge32_op:
103 if (!cpu_has_dsp) 107 if (!cpu_has_dsp)
104 goto sigill; 108 goto sigill;
@@ -133,9 +137,11 @@ int __compute_return_epc(struct pt_regs *regs)
133 case beq_op: 137 case beq_op:
134 case beql_op: 138 case beql_op:
135 if (regs->regs[insn.i_format.rs] == 139 if (regs->regs[insn.i_format.rs] ==
136 regs->regs[insn.i_format.rt]) 140 regs->regs[insn.i_format.rt]) {
137 epc = epc + 4 + (insn.i_format.simmediate << 2); 141 epc = epc + 4 + (insn.i_format.simmediate << 2);
138 else 142 if (insn.i_format.rt == beql_op)
143 ret = BRANCH_LIKELY_TAKEN;
144 } else
139 epc += 8; 145 epc += 8;
140 regs->cp0_epc = epc; 146 regs->cp0_epc = epc;
141 break; 147 break;
@@ -143,9 +149,11 @@ int __compute_return_epc(struct pt_regs *regs)
143 case bne_op: 149 case bne_op:
144 case bnel_op: 150 case bnel_op:
145 if (regs->regs[insn.i_format.rs] != 151 if (regs->regs[insn.i_format.rs] !=
146 regs->regs[insn.i_format.rt]) 152 regs->regs[insn.i_format.rt]) {
147 epc = epc + 4 + (insn.i_format.simmediate << 2); 153 epc = epc + 4 + (insn.i_format.simmediate << 2);
148 else 154 if (insn.i_format.rt == bnel_op)
155 ret = BRANCH_LIKELY_TAKEN;
156 } else
149 epc += 8; 157 epc += 8;
150 regs->cp0_epc = epc; 158 regs->cp0_epc = epc;
151 break; 159 break;
@@ -153,9 +161,11 @@ int __compute_return_epc(struct pt_regs *regs)
153 case blez_op: /* not really i_format */ 161 case blez_op: /* not really i_format */
154 case blezl_op: 162 case blezl_op:
155 /* rt field assumed to be zero */ 163 /* rt field assumed to be zero */
156 if ((long)regs->regs[insn.i_format.rs] <= 0) 164 if ((long)regs->regs[insn.i_format.rs] <= 0) {
157 epc = epc + 4 + (insn.i_format.simmediate << 2); 165 epc = epc + 4 + (insn.i_format.simmediate << 2);
158 else 166 if (insn.i_format.rt == bnel_op)
167 ret = BRANCH_LIKELY_TAKEN;
168 } else
159 epc += 8; 169 epc += 8;
160 regs->cp0_epc = epc; 170 regs->cp0_epc = epc;
161 break; 171 break;
@@ -163,9 +173,11 @@ int __compute_return_epc(struct pt_regs *regs)
163 case bgtz_op: 173 case bgtz_op:
164 case bgtzl_op: 174 case bgtzl_op:
165 /* rt field assumed to be zero */ 175 /* rt field assumed to be zero */
166 if ((long)regs->regs[insn.i_format.rs] > 0) 176 if ((long)regs->regs[insn.i_format.rs] > 0) {
167 epc = epc + 4 + (insn.i_format.simmediate << 2); 177 epc = epc + 4 + (insn.i_format.simmediate << 2);
168 else 178 if (insn.i_format.rt == bnel_op)
179 ret = BRANCH_LIKELY_TAKEN;
180 } else
169 epc += 8; 181 epc += 8;
170 regs->cp0_epc = epc; 182 regs->cp0_epc = epc;
171 break; 183 break;
@@ -187,18 +199,22 @@ int __compute_return_epc(struct pt_regs *regs)
187 switch (insn.i_format.rt & 3) { 199 switch (insn.i_format.rt & 3) {
188 case 0: /* bc1f */ 200 case 0: /* bc1f */
189 case 2: /* bc1fl */ 201 case 2: /* bc1fl */
190 if (~fcr31 & (1 << bit)) 202 if (~fcr31 & (1 << bit)) {
191 epc = epc + 4 + (insn.i_format.simmediate << 2); 203 epc = epc + 4 + (insn.i_format.simmediate << 2);
192 else 204 if (insn.i_format.rt == 2)
205 ret = BRANCH_LIKELY_TAKEN;
206 } else
193 epc += 8; 207 epc += 8;
194 regs->cp0_epc = epc; 208 regs->cp0_epc = epc;
195 break; 209 break;
196 210
197 case 1: /* bc1t */ 211 case 1: /* bc1t */
198 case 3: /* bc1tl */ 212 case 3: /* bc1tl */
199 if (fcr31 & (1 << bit)) 213 if (fcr31 & (1 << bit)) {
200 epc = epc + 4 + (insn.i_format.simmediate << 2); 214 epc = epc + 4 + (insn.i_format.simmediate << 2);
201 else 215 if (insn.i_format.rt == 3)
216 ret = BRANCH_LIKELY_TAKEN;
217 } else
202 epc += 8; 218 epc += 8;
203 regs->cp0_epc = epc; 219 regs->cp0_epc = epc;
204 break; 220 break;
@@ -239,15 +255,39 @@ int __compute_return_epc(struct pt_regs *regs)
239#endif 255#endif
240 } 256 }
241 257
242 return 0; 258 return ret;
243 259
244unaligned: 260sigill:
245 printk("%s: unaligned epc - sending SIGBUS.\n", current->comm); 261 printk("%s: DSP branch but not DSP ASE - sending SIGBUS.\n", current->comm);
246 force_sig(SIGBUS, current); 262 force_sig(SIGBUS, current);
247 return -EFAULT; 263 return -EFAULT;
264}
265EXPORT_SYMBOL_GPL(__compute_return_epc_for_insn);
248 266
249sigill: 267int __compute_return_epc(struct pt_regs *regs)
250 printk("%s: DSP branch but not DSP ASE - sending SIGBUS.\n", current->comm); 268{
269 unsigned int __user *addr;
270 long epc;
271 union mips_instruction insn;
272
273 epc = regs->cp0_epc;
274 if (epc & 3)
275 goto unaligned;
276
277 /*
278 * Read the instruction
279 */
280 addr = (unsigned int __user *) epc;
281 if (__get_user(insn.word, addr)) {
282 force_sig(SIGSEGV, current);
283 return -EFAULT;
284 }
285
286 return __compute_return_epc_for_insn(regs, insn);
287
288unaligned:
289 printk("%s: unaligned epc - sending SIGBUS.\n", current->comm);
251 force_sig(SIGBUS, current); 290 force_sig(SIGBUS, current);
252 return -EFAULT; 291 return -EFAULT;
292
253} 293}