diff options
Diffstat (limited to 'arch/mips/kernel/branch.c')
-rw-r--r-- | arch/mips/kernel/branch.c | 128 |
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 | */ |
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 | } |