diff options
author | Maneesh Soni <manesoni@cisco.com> | 2011-11-08 06:37:11 -0500 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2011-12-07 17:04:03 -0500 |
commit | d8d4e3ae0b5c179c0bfd3f0af5b352d13bea9cfa (patch) | |
tree | e0341b09add0b344ce078595d3ad5145beda0cfd /arch | |
parent | 9233c1ee71bdd3c8a918c8e17026cf3f7d99c90b (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')
-rw-r--r-- | arch/mips/include/asm/branch.h | 5 | ||||
-rw-r--r-- | arch/mips/kernel/branch.c | 128 | ||||
-rw-r--r-- | arch/mips/math-emu/cp1emu.c | 2 |
3 files changed, 90 insertions, 45 deletions
diff --git a/arch/mips/include/asm/branch.h b/arch/mips/include/asm/branch.h index 37c6857c8d4a..888766ae1f85 100644 --- a/arch/mips/include/asm/branch.h +++ b/arch/mips/include/asm/branch.h | |||
@@ -9,6 +9,7 @@ | |||
9 | #define _ASM_BRANCH_H | 9 | #define _ASM_BRANCH_H |
10 | 10 | ||
11 | #include <asm/ptrace.h> | 11 | #include <asm/ptrace.h> |
12 | #include <asm/inst.h> | ||
12 | 13 | ||
13 | static inline int delay_slot(struct pt_regs *regs) | 14 | static inline int delay_slot(struct pt_regs *regs) |
14 | { | 15 | { |
@@ -23,7 +24,11 @@ static inline unsigned long exception_epc(struct pt_regs *regs) | |||
23 | return regs->cp0_epc + 4; | 24 | return regs->cp0_epc + 4; |
24 | } | 25 | } |
25 | 26 | ||
27 | #define BRANCH_LIKELY_TAKEN 0x0001 | ||
28 | |||
26 | extern int __compute_return_epc(struct pt_regs *regs); | 29 | extern int __compute_return_epc(struct pt_regs *regs); |
30 | extern int __compute_return_epc_for_insn(struct pt_regs *regs, | ||
31 | union mips_instruction insn); | ||
27 | 32 | ||
28 | static inline int compute_return_epc(struct pt_regs *regs) | 33 | static inline int compute_return_epc(struct pt_regs *regs) |
29 | { | 34 | { |
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 | */ |
23 | int __compute_return_epc(struct pt_regs *regs) | 31 | int __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 | ||
244 | unaligned: | 260 | sigill: |
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 | } | ||
265 | EXPORT_SYMBOL_GPL(__compute_return_epc_for_insn); | ||
248 | 266 | ||
249 | sigill: | 267 | int __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 | |||
288 | unaligned: | ||
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 | } |
diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c index dbf2f93a5091..a03bf00a1a9c 100644 --- a/arch/mips/math-emu/cp1emu.c +++ b/arch/mips/math-emu/cp1emu.c | |||
@@ -245,7 +245,7 @@ static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx, | |||
245 | */ | 245 | */ |
246 | emulpc = xcp->cp0_epc + 4; /* Snapshot emulation target */ | 246 | emulpc = xcp->cp0_epc + 4; /* Snapshot emulation target */ |
247 | 247 | ||
248 | if (__compute_return_epc(xcp)) { | 248 | if (__compute_return_epc(xcp) < 0) { |
249 | #ifdef CP1DBG | 249 | #ifdef CP1DBG |
250 | printk("failed to emulate branch at %p\n", | 250 | printk("failed to emulate branch at %p\n", |
251 | (void *) (xcp->cp0_epc)); | 251 | (void *) (xcp->cp0_epc)); |